├── Directory.Build.targets
├── Tests
├── UnitTests
│ ├── Todo.WebApi.UnitTests
│ │ ├── Properties
│ │ │ └── AssemblyInfo.cs
│ │ ├── Controllers
│ │ │ ├── VerifySnapshots
│ │ │ │ ├── HealthCheckControllerTests.GetHealthReportAsync_WhenTimeoutExceptionOccurs_ReturnsExpectedHealthReport.verified.txt
│ │ │ │ └── HealthCheckControllerTests.GetHealthReportAsync_WhenUnexpectedExceptionOccurs_ReturnsExpectedHealthReport.verified.txt
│ │ │ └── HealthCheckControllerTests.cs
│ │ ├── ModuleInitializer.cs
│ │ ├── Todo.WebApi.UnitTests.csproj
│ │ └── ExceptionHandling
│ │ │ └── ExceptionMappingResultsTests.cs
│ ├── Todo.Services.UnitTests
│ │ ├── Properties
│ │ │ └── AssemblyInfo.cs
│ │ ├── ModuleInitializer.cs
│ │ ├── Security
│ │ │ ├── VerifySnapshots
│ │ │ │ └── JwtServiceTests.GenerateJwtAsync_WhenUsingValidInput_MustReturnExpectedResult.verified.txt
│ │ │ └── JwtServiceTests.cs
│ │ └── Todo.Services.UnitTests.csproj
│ ├── Todo.Telemetry.UnitTests
│ │ ├── Properties
│ │ │ └── AssemblyInfo.cs
│ │ ├── Serilog
│ │ │ └── ConfigurationExtensions.cs
│ │ └── Todo.Telemetry.UnitTests.csproj
│ └── Todo.ApplicationFlows.UnitTests
│ │ ├── Properties
│ │ └── AssemblyInfo.cs
│ │ └── Todo.ApplicationFlows.UnitTests.csproj
├── ArchitectureTests
│ └── Todo.ArchitectureTests
│ │ ├── Properties
│ │ └── AssemblyInfo.cs
│ │ └── Todo.ArchitectureTests.csproj
├── IntegrationTests
│ ├── Todo.WebApi.IntegrationTests
│ │ ├── Properties
│ │ │ └── AssemblyInfo.cs
│ │ ├── Todo.WebApi.IntegrationTests.csproj.user
│ │ ├── VerifyChecksTests.cs
│ │ ├── Controllers
│ │ │ ├── VerifySnapshots
│ │ │ │ ├── ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=Staging_expectedStatusCode=Forbidden.verified.txt
│ │ │ │ ├── ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=DemoInAzure_expectedStatusCode=Forbidden.verified.txt
│ │ │ │ ├── ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=Production_expectedStatusCode=Forbidden.verified.txt
│ │ │ │ ├── ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=AcceptanceTests_expectedStatusCode=Forbidden.verified.txt
│ │ │ │ ├── ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=IntegrationTests_expectedStatusCode=Forbidden.verified.txt
│ │ │ │ ├── TodoControllerTests.DeleteAsync_UsingNewlyCreatedTodoItem_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── TodoControllerTests.DeleteAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── TodoControllerTests.GetByIdAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── HealthCheckControllerTests.GetHealthReportAsync_WhenRequestIsNotAuthorized_ReturnsUnauthorizedHttpStatusCode.verified.txt
│ │ │ │ ├── TodoControllerTests.GetByQueryAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=Development_expectedStatusCode=OK.verified.txt
│ │ │ │ ├── TodoControllerTests.GetByQueryAsync_UsingDefaults_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── TodoControllerTests.GetByIdAsync_UsingNewlyCreatedItem_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── HealthCheckControllerTests.GetHealthReportAsync_UsingValidInput_ReturnsExpectedHealthReport.verified.txt
│ │ │ │ ├── TodoControllerTests.GetByIdAsync_UsingNonExistingId_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── TodoControllerTests.CreateAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── TodoControllerTests.UpdateAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── TodoControllerTests.UpdateAsync_UsingNewlyCreatedTodoItem_ReturnsExpectedResult.verified.txt
│ │ │ │ ├── TodoControllerTests.CreateAsync_UsingInvalidTodoItem_ReturnsExpectedResult.verified.txt
│ │ │ │ └── TodoControllerTests.CreateAsync_UsingValidTodoItemReturnsExpectedResult.verified.txt
│ │ │ └── ConfigurationControllerTests.cs
│ │ ├── ModuleInitializer.cs
│ │ └── Todo.WebApi.IntegrationTests.csproj
│ ├── Todo.Persistence.IntegrationTests
│ │ ├── Properties
│ │ │ └── AssemblyInfo.cs
│ │ └── Todo.Persistence.IntegrationTests.csproj
│ └── Todo.ApplicationFlows.IntegrationTests
│ │ ├── Properties
│ │ └── AssemblyInfo.cs
│ │ ├── VerifySnapshots
│ │ └── TransactionalBaseApplicationFlowTests.ExecuteAsync_WhenAllStepsSucceeds_MustSucceed.verified.txt
│ │ ├── ModuleInitializer.cs
│ │ └── Todo.ApplicationFlows.IntegrationTests.csproj
├── AcceptanceTests
│ └── Todo.WebApi.AcceptanceTests
│ │ ├── Properties
│ │ └── AssemblyInfo.cs
│ │ ├── Drivers
│ │ ├── UserDetails.cs
│ │ └── NewTodoItemInfo.cs
│ │ ├── Infrastructure
│ │ ├── TcpPortProvider.cs
│ │ ├── SetupSystemUnderTest.cs
│ │ └── ScenarioDependencies.cs
│ │ ├── Todo.WebApi.AcceptanceTests.csproj
│ │ └── Features
│ │ └── AddTodoItem.feature
├── Infrastructure
│ ├── Directory.Build.props
│ └── Todo.WebApi.TestInfrastructure
│ │ ├── CouldNotGetJwtTokenException.cs
│ │ └── Todo.WebApi.TestInfrastructure.csproj
├── Directory.Build.props
├── Directory.Build.targets
└── .artifactignore
├── .gitattributes
├── Sources
├── Todo.WebApi
│ ├── Models
│ │ ├── JwtModel.cs
│ │ ├── GenerateJwtModel.cs
│ │ ├── TodoItemModel.cs
│ │ ├── GenerateJwtOptions.cs
│ │ ├── NewTodoItemModel.cs
│ │ ├── UpdateTodoItemModel.cs
│ │ └── TodoItemQueryModel.cs
│ ├── ExceptionHandling
│ │ ├── ExceptionMappingResult.cs
│ │ ├── Configuration
│ │ │ └── ExceptionHandlingOptions.cs
│ │ └── ExceptionMappingResults.cs
│ ├── Authorization
│ │ ├── Policies.cs
│ │ ├── HasScopeRequirement.cs
│ │ └── HasScopeHandler.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Todo.WebApi.csproj
│ ├── appsettings.DemoInAzure.json
│ ├── Program.cs
│ ├── appsettings.Development.json
│ ├── appsettings.IntegrationTests.json
│ ├── Controllers
│ │ └── ConfigurationController.cs
│ └── appsettings.AcceptanceTests.json
├── Todo.Services
│ ├── Security
│ │ ├── JwtInfo.cs
│ │ ├── IJwtService.cs
│ │ ├── GenerateJwtInfo.cs
│ │ ├── JwtService.cs
│ │ └── PrincipalExtensions.cs
│ ├── Todo.Services.csproj
│ ├── TodoItemManagement
│ │ ├── DeleteTodoItemInfo.cs
│ │ ├── TodoItemInfo.cs
│ │ ├── NewTodoItemInfo.cs
│ │ ├── EntityNotFoundException.cs
│ │ ├── UpdateTodoItemInfo.cs
│ │ ├── ITodoItemService.cs
│ │ └── TodoItemQuery.cs
│ └── DependencyInjection
│ │ └── ServicesModule.cs
├── Todo.Commons
│ ├── Constants
│ │ ├── EnvironmentVariables.cs
│ │ ├── Logging.cs
│ │ ├── ConnectionStrings.cs
│ │ └── EnvironmentNames.cs
│ ├── StartupLogic
│ │ ├── IStartupLogicTask.cs
│ │ ├── IStartupLogicTaskExecutor.cs
│ │ └── HostExtensions.cs
│ ├── Todo.Commons.csproj
│ └── Diagnostics
│ │ └── ActivitySources.cs
├── Todo.Telemetry
│ ├── OpenTelemetry
│ │ └── Configuration
│ │ │ ├── Exporters
│ │ │ ├── AzureMonitorOptions.cs
│ │ │ ├── OpenTelemetryExporterOptions.cs
│ │ │ └── JaegerOptions.cs
│ │ │ ├── Instrumentation
│ │ │ ├── EntityFrameworkCoreOptions.cs
│ │ │ └── OpenTelemetryInstrumentationOptions.cs
│ │ │ ├── Logging
│ │ │ └── LoggingOptions.cs
│ │ │ └── OpenTelemetryOptions.cs
│ ├── Http
│ │ ├── IHttpContextLoggingHandler.cs
│ │ ├── HttpLoggingMiddlewareExtensions.cs
│ │ ├── ConversationIdProviderMiddlewareExtensions.cs
│ │ ├── IHttpObjectConverter.cs
│ │ ├── StreamExtensions.cs
│ │ ├── HttpLoggingActivator.cs
│ │ └── ConversationIdProviderMiddleware.cs
│ ├── Serilog
│ │ ├── SerilogConstants.cs
│ │ ├── Destructuring
│ │ │ ├── NewTodoItemInfoDestructuringPolicy.cs
│ │ │ ├── DeleteTodoItemInfoDestructuringPolicy.cs
│ │ │ ├── UpdateTodoItemInfoDestructuringPolicy.cs
│ │ │ └── TodoItemQueryDestructuringPolicy.cs
│ │ └── FileSinkMetadataLogger.cs
│ ├── DependencyInjection
│ │ └── TelemetryModule.cs
│ └── Todo.Telemetry.csproj
├── Todo.Persistence
│ ├── Migrations
│ │ ├── TodoDbContextModelSnapshot.CustomAttributes.cs
│ │ ├── 20200124210915_AddIndexForNameColumnInsideTodoItemsTable.cs
│ │ ├── 20200124211005_AddIndexForCreatedByColumnInsideTodoItemsTable.cs
│ │ ├── 20200515210035_AddSupportForOptimisticLockingToTodoTable.cs
│ │ ├── 20221117205342_AddVersionColumnToTheTodoItemsTable.cs
│ │ ├── 20200124212126_ConsolidateCreatedByAndNameColumnsIntoAnUniqueIndexInsideTodoItemsTable.cs
│ │ ├── 20200124210404_InitialSchema.cs
│ │ ├── 20200124210404_InitialSchema.Designer.cs
│ │ ├── 20200124210915_AddIndexForNameColumnInsideTodoItemsTable.Designer.cs
│ │ ├── 20200124211005_AddIndexForCreatedByColumnInsideTodoItemsTable.Designer.cs
│ │ ├── 20200124212126_ConsolidateCreatedByAndNameColumnsIntoAnUniqueIndexInsideTodoItemsTable.Designer.cs
│ │ ├── TodoDbContextModelSnapshot.cs
│ │ └── 20200515210035_AddSupportForOptimisticLockingToTodoTable.Designer.cs
│ ├── Todo.Persistence.csproj
│ ├── TodoDbContext.cs
│ ├── Entities
│ │ └── Configurations
│ │ │ └── TodoItemConfiguration.cs
│ └── DependencyInjection
│ │ └── ServiceCollectionExtensions.cs
├── Todo.ApplicationFlows
│ ├── Todo.ApplicationFlows.csproj
│ ├── Security
│ │ ├── IGenerateJwtFlow.cs
│ │ └── GenerateJwtFlow.cs
│ ├── TodoItems
│ │ ├── IFetchTodoItemByIdFlow.cs
│ │ ├── IFetchTodoItemsFlow.cs
│ │ ├── IAddTodoItemFlow.cs
│ │ ├── IDeleteTodoItemFlow.cs
│ │ ├── IUpdateTodoItemFlow.cs
│ │ ├── AddTodoItemFlow.cs
│ │ ├── UpdateTodoItemFlow.cs
│ │ ├── DeleteTodoItemFlow.cs
│ │ ├── FetchTodoItemsFlow.cs
│ │ └── FetchTodoItemByIdFlow.cs
│ ├── ApplicationFlowOptions.cs
│ ├── TransactionOptions.cs
│ ├── IApplicationFlow.cs
│ ├── SimpleApplicationFlow.cs
│ ├── ApplicationEvents
│ │ └── StartupLogicTaskExecutor.cs
│ ├── TransactionalBaseApplicationFlow.cs
│ └── DependencyInjection
│ │ └── ApplicationFlowsModule.cs
└── Directory.Build.props
├── .sonarlint
├── sonar.settings.json
├── Todo.slconfig
├── Todo.WebApi.slconfig
├── aspnet-core-logging_secrets_settings.json
└── aspnet-core-logging
│ └── CSharp
│ └── SonarLint.xml
├── .gitignore
├── LICENSE
├── Build
├── db4tests
│ └── docker-compose.yml
├── start-docker-on-macOS.sh
└── SonarBuildBreaker.ps1
├── Directory.Build.props
└── nuget.config
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.WebApi.UnitTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("UnitTests")]
2 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.Services.UnitTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("UnitTests")]
2 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.Telemetry.UnitTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("UnitTests")]
2 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.ApplicationFlows.UnitTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("UnitTests")]
2 |
--------------------------------------------------------------------------------
/Tests/ArchitectureTests/Todo.ArchitectureTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("ArchitectureTests")]
2 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("IntegrationTests")]
2 |
--------------------------------------------------------------------------------
/Tests/AcceptanceTests/Todo.WebApi.AcceptanceTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: Xunit.AssemblyTrait("TestCategory", "AcceptanceTests")]
2 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.Persistence.IntegrationTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("IntegrationTests")]
2 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.ApplicationFlows.IntegrationTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: NUnit.Framework.Category("IntegrationTests")]
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.verified.txt text eol=lf working-tree-encoding=UTF-8
2 | *.verified.xml text eol=lf working-tree-encoding=UTF-8
3 | *.verified.json text eol=lf working-tree-encoding=UTF-8
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Models/JwtModel.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Models
2 | {
3 | public class JwtModel
4 | {
5 | public string AccessToken { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/Security/JwtInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.Security
2 | {
3 | public class JwtInfo
4 | {
5 | public string AccessToken { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Tests/AcceptanceTests/Todo.WebApi.AcceptanceTests/Drivers/UserDetails.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.AcceptanceTests.Drivers
2 | {
3 | public record UserDetails(string UserName, string Password);
4 | }
5 |
--------------------------------------------------------------------------------
/Tests/AcceptanceTests/Todo.WebApi.AcceptanceTests/Drivers/NewTodoItemInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.AcceptanceTests.Drivers
2 | {
3 | public record NewTodoItemInfo(string Name, bool IsComplete);
4 | }
5 |
--------------------------------------------------------------------------------
/.sonarlint/sonar.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "sonar.exclusions": [
3 | ],
4 | "sonar.global.exclusions": [
5 | "**/build-wrapper-dump.json"
6 | ],
7 | "sonar.inclusions": [
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/Sources/Todo.Commons/Constants/EnvironmentVariables.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Commons.Constants
2 | {
3 | public static class EnvironmentVariables
4 | {
5 | public const string Prefix = "TODO_WEB_API_BY_SATRAPU_";
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/ExceptionHandling/ExceptionMappingResult.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.ExceptionHandling
2 | {
3 | using System.Net;
4 |
5 | public sealed record ExceptionMappingResult(HttpStatusCode HttpStatusCode, string RootCauseKey);
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Models/GenerateJwtModel.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Models
2 | {
3 | public class GenerateJwtModel
4 | {
5 | public string UserName { get; set; }
6 |
7 | public string Password { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/OpenTelemetry/Configuration/Exporters/AzureMonitorOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.OpenTelemetry.Configuration.Exporters
2 | {
3 | public class AzureMonitorOptions
4 | {
5 | public bool Enabled { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Sources/Todo.Persistence/Migrations/TodoDbContextModelSnapshot.CustomAttributes.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Persistence.Migrations
2 | {
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | [ExcludeFromCodeCoverage]
6 | public partial class TodoDbContextModelSnapshot
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/OpenTelemetry/Configuration/Instrumentation/EntityFrameworkCoreOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.OpenTelemetry.Configuration.Instrumentation
2 | {
3 | public class EntityFrameworkCoreOptions
4 | {
5 | public bool SetDbStatementForText { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/Security/IJwtService.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.Security
2 | {
3 | using System.Threading.Tasks;
4 |
5 | ///
6 | ///
7 | ///
8 | public interface IJwtService
9 | {
10 | Task GenerateJwtAsync(GenerateJwtInfo generateJwtInfo);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/OpenTelemetry/Configuration/Instrumentation/OpenTelemetryInstrumentationOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.OpenTelemetry.Configuration.Instrumentation
2 | {
3 | public class OpenTelemetryInstrumentationOptions
4 | {
5 | public EntityFrameworkCoreOptions EntityFrameworkCore { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Todo.WebApi.IntegrationTests.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | IIS Express
5 |
6 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/OpenTelemetry/Configuration/Exporters/OpenTelemetryExporterOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.OpenTelemetry.Configuration.Exporters
2 | {
3 | public class OpenTelemetryExporterOptions
4 | {
5 | public AzureMonitorOptions AzureMonitor { get; set; }
6 | public JaegerOptions Jaeger { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/OpenTelemetry/Configuration/Exporters/JaegerOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.OpenTelemetry.Configuration.Exporters
2 | {
3 | public class JaegerOptions
4 | {
5 | public string AgentHost { get; set; }
6 |
7 | public int AgentPort { get; set; }
8 |
9 | public bool Enabled { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/VerifyChecksTests.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi
2 | {
3 | using System.Threading.Tasks;
4 |
5 | using NUnit.Framework;
6 |
7 | using VerifyNUnit;
8 |
9 | [TestFixture]
10 | public class VerifyChecksTests
11 | {
12 | [Test]
13 | public Task Run() => VerifyChecks.Run();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/Todo.ApplicationFlows.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.sonarlint/Todo.slconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ServerUri": "https://sonarcloud.io/",
3 | "Organization": {
4 | "Key": "satrapu-github",
5 | "Name": "Bogdan Marian"
6 | },
7 | "ProjectKey": "aspnet-core-logging",
8 | "ProjectName": "aspnet-core-logging",
9 | "Profiles": {
10 | "CSharp": {
11 | "ProfileKey": "AVxYJ9ttFTbgxqUNcKz1",
12 | "ProfileTimestamp": "2022-07-05T10:19:10Z"
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.WebApi.UnitTests/Controllers/VerifySnapshots/HealthCheckControllerTests.GetHealthReportAsync_WhenTimeoutExceptionOccurs_ReturnsExpectedHealthReport.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Value: {
3 | HealthReport: {
4 | Status: Unhealthy,
5 | Description: Failed to check dependencies due to a timeout,
6 | Duration: 0:00:02,
7 | Dependencies: []
8 | }
9 | },
10 | StatusCode: 503
11 | }
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/Security/IGenerateJwtFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.Security
2 | {
3 | using Todo.Services.Security;
4 |
5 | ///
6 | /// Application flow used for generating JSON web tokens needed for authentication and authorization purposes.
7 | ///
8 | public interface IGenerateJwtFlow : IApplicationFlow
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.WebApi.UnitTests/Controllers/VerifySnapshots/HealthCheckControllerTests.GetHealthReportAsync_WhenUnexpectedExceptionOccurs_ReturnsExpectedHealthReport.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Value: {
3 | HealthReport: {
4 | Status: Unhealthy,
5 | Description: Failed to check dependencies due to an unexpected error,
6 | Duration: 0:00:02,
7 | Dependencies: []
8 | }
9 | },
10 | StatusCode: 503
11 | }
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TodoItems/IFetchTodoItemByIdFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.TodoItems
2 | {
3 | using Services.TodoItemManagement;
4 |
5 | ///
6 | /// Application flow used for fetching a instance matching a given identifier.
7 | ///
8 | public interface IFetchTodoItemByIdFlow : IApplicationFlow
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/Todo.Services.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Sources/Todo.Commons/StartupLogic/IStartupLogicTask.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Commons.StartupLogic
2 | {
3 | using System.Threading.Tasks;
4 |
5 | ///
6 | /// Executes logic during application startup.
7 | ///
8 | public interface IStartupLogicTask
9 | {
10 | ///
11 | /// Executes logic during application startup.
12 | ///
13 | Task ExecuteAsync();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Todo.Commons/Todo.Commons.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/TodoItemManagement/DeleteTodoItemInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.TodoItemManagement
2 | {
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Security.Principal;
5 |
6 | public class DeleteTodoItemInfo
7 | {
8 | [Required]
9 | [Range(1, long.MaxValue)]
10 | public long? Id { get; set; }
11 |
12 | [Required]
13 | public IPrincipal Owner { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/Infrastructure/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | false
9 | false
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TodoItems/IFetchTodoItemsFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.TodoItems
2 | {
3 | using System.Collections.Generic;
4 |
5 | using Services.TodoItemManagement;
6 |
7 | ///
8 | /// Application flow used for fetching instances matching a given query.
9 | ///
10 | public interface IFetchTodoItemsFlow : IApplicationFlow>
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/OpenTelemetry/Configuration/Logging/LoggingOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.OpenTelemetry.Configuration.Logging
2 | {
3 | public class LoggingOptions
4 | {
5 | public bool AttachLogsToActivity { get; set; }
6 |
7 | public bool Enabled { get; set; }
8 |
9 | public bool IncludeScopes { get; set; }
10 |
11 | public bool IncludeFormattedMessage { get; set; }
12 |
13 | public bool ParseStateValues { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TodoItems/IAddTodoItemFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.TodoItems
2 | {
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | using Services.TodoItemManagement;
6 |
7 | ///
8 | /// Application flow used for creating a new todo item.
9 | ///
10 | [SuppressMessage("ReSharper", "S1135", Justification = "The todo word represents an entity")]
11 | public interface IAddTodoItemFlow : IApplicationFlow
12 | {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TodoItems/IDeleteTodoItemFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.TodoItems
2 | {
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | using Services.TodoItemManagement;
6 |
7 | ///
8 | /// Application flow used for deleting an existing todo item.
9 | ///
10 | [SuppressMessage("ReSharper", "S1135", Justification = "The todo word represents an entity")]
11 | public interface IDeleteTodoItemFlow : IApplicationFlow
12 | {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TodoItems/IUpdateTodoItemFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.TodoItems
2 | {
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | using Services.TodoItemManagement;
6 |
7 | ///
8 | /// Application flow used for updating an existing todo item.
9 | ///
10 | [SuppressMessage("ReSharper", "S1135", Justification = "The todo word represents an entity")]
11 | public interface IUpdateTodoItemFlow : IApplicationFlow
12 | {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Todo.Commons/StartupLogic/IStartupLogicTaskExecutor.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Commons.StartupLogic
2 | {
3 | using System.Threading.Tasks;
4 |
5 | ///
6 | /// Executes application startup logic by invoking all registered instances.
7 | ///
8 | public interface IStartupLogicTaskExecutor
9 | {
10 | ///
11 | /// Executes all registered instances.
12 | ///
13 | Task ExecuteAsync();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.sonarlint/Todo.WebApi.slconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ServerUri": "https://sonarcloud.io/",
3 | "Organization": {
4 | "Key": "satrapu-github",
5 | "Name": "Bogdan Marian"
6 | },
7 | "ProjectKey": "aspnet-core-logging",
8 | "ProjectName": "aspnet-core-logging",
9 | "Profiles": {
10 | "Secrets": {
11 | "ProfileKey": "AYXoTKev9Ao2yLWbM_j-",
12 | "ProfileTimestamp": "2023-01-25T09:40:14Z"
13 | },
14 | "CSharp": {
15 | "ProfileKey": "AVxYJ9ttFTbgxqUNcKz1",
16 | "ProfileTimestamp": "2023-03-09T14:55:47Z"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/OpenTelemetry/Configuration/OpenTelemetryOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.OpenTelemetry.Configuration
2 | {
3 | using Exporters;
4 |
5 | using Instrumentation;
6 |
7 | using Logging;
8 |
9 | public class OpenTelemetryOptions
10 | {
11 | public bool Enabled { get; set; }
12 |
13 | public LoggingOptions Logging { get; set; }
14 |
15 | public OpenTelemetryInstrumentationOptions Instrumentation { get; set; }
16 |
17 | public OpenTelemetryExporterOptions Exporters { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/TodoItemManagement/TodoItemInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.TodoItemManagement
2 | {
3 | using System;
4 |
5 | public class TodoItemInfo
6 | {
7 | public long Id { get; set; }
8 |
9 | public string Name { get; set; }
10 |
11 | public bool IsComplete { get; set; }
12 |
13 | public string CreatedBy { get; set; }
14 |
15 | public DateTime CreatedOn { get; set; }
16 |
17 | public string LastUpdatedBy { get; set; }
18 |
19 | public DateTime? LastUpdatedOn { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/TodoItemManagement/NewTodoItemInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.TodoItemManagement
2 | {
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Security.Principal;
5 |
6 | public class NewTodoItemInfo
7 | {
8 | [Required(AllowEmptyStrings = false)]
9 | [MinLength(2)]
10 | [MaxLength(100)]
11 | public string Name { get; set; }
12 |
13 | [Required]
14 | public bool? IsComplete { get; set; }
15 |
16 | [Required]
17 | public IPrincipal Owner { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Models/TodoItemModel.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable UnusedAutoPropertyAccessor.Global
2 |
3 | namespace Todo.WebApi.Models
4 | {
5 | using System;
6 |
7 | public class TodoItemModel
8 | {
9 | public long Id { get; set; }
10 |
11 | public string Name { get; set; }
12 |
13 | public bool IsComplete { get; set; }
14 |
15 | public string CreatedBy { get; set; }
16 |
17 | public DateTime CreatedOn { get; set; }
18 |
19 | public string LastUpdatedBy { get; set; }
20 |
21 | public DateTime? LastUpdatedOn { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Models/GenerateJwtOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Models
2 | {
3 | ///
4 | /// Contains options used when generating JSON web tokens.
5 | ///
6 | public class GenerateJwtOptions
7 | {
8 | ///
9 | /// Gets or sets the audience for the JSON web tokens.
10 | ///
11 | public string Audience { get; set; }
12 |
13 | ///
14 | /// Gets or sets
15 | ///
16 | public string Issuer { get; set; }
17 |
18 | public string Secret { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Tests/Infrastructure/Todo.WebApi.TestInfrastructure/CouldNotGetJwtTokenException.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.TestInfrastructure
2 | {
3 | using System;
4 | using System.Net.Http;
5 |
6 | public class CouldNotGetJwtException : Exception
7 | {
8 | public CouldNotGetJwtException(HttpResponseMessage httpResponseMessage) :
9 | base($"Failed to get an JSON web token for testing purposes due to: [{httpResponseMessage.ReasonPhrase}]. "
10 | + $"Detailed error message is: [{httpResponseMessage.Content.ReadAsStringAsync().Result}]")
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/Http/IHttpContextLoggingHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.Http
2 | {
3 | using Microsoft.AspNetCore.Http;
4 |
5 | ///
6 | /// Handles the logic of logging instances.
7 | ///
8 | public interface IHttpContextLoggingHandler
9 | {
10 | ///
11 | /// Checks whether the given should be logged or not.
12 | ///
13 | ///
14 | ///
15 | bool ShouldLog(HttpContext httpContext);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Authorization/Policies.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Authorization
2 | {
3 | public static class Policies
4 | {
5 | public static class Infrastructure
6 | {
7 | public const string HealthCheck = "get:health";
8 | }
9 |
10 | public static class TodoItems
11 | {
12 | public const string CreateTodoItem = "create:todo";
13 | public const string UpdateTodoItem = "update:todo";
14 | public const string DeleteTodoItem = "delete:todo";
15 | public const string GetTodoItems = "get:todo";
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Todo.Persistence/Todo.Persistence.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/ApplicationFlowOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows
2 | {
3 | ///
4 | /// Configures the behavior of a particular application flow.
5 | ///
6 | // ReSharper disable once ClassNeverInstantiated.Global
7 | public class ApplicationFlowOptions
8 | {
9 | ///
10 | /// Gets or sets the options to use when configuring the transaction used by a particular application flow.
11 | ///
12 | // ReSharper disable once UnusedAutoPropertyAccessor.Global
13 | public TransactionOptions TransactionOptions { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 | true
7 | true
8 | ..\..\..\.sonarlint\aspnet-core-loggingCSharp.ruleset
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Tests/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/TodoItemManagement/EntityNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.TodoItemManagement
2 | {
3 | using System;
4 |
5 | ///
6 | /// Thrown when failed to fetch an entity using a key.
7 | ///
8 | public class EntityNotFoundException : Exception
9 | {
10 | public EntityNotFoundException(Type entityType, object entityKey)
11 | : base($"Could not find entity of type \"{entityType?.FullName}\" using key \"{entityKey}\"")
12 | {
13 | base.Data.Add("EntityType", entityType?.FullName);
14 | base.Data.Add("EntityKey", entityKey);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Models/NewTodoItemModel.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Models
2 | {
3 | using System.ComponentModel.DataAnnotations;
4 |
5 | public class NewTodoItemModel
6 | {
7 | ///
8 | /// Gets or sets the name of this to do item.
9 | ///
10 | [Required(AllowEmptyStrings = false)]
11 | [MinLength(2)]
12 | [MaxLength(100)]
13 | public string Name { get; set; }
14 |
15 | ///
16 | /// Gets or sets whether this to do item has been completed.
17 | ///
18 | [Required]
19 | public bool? IsComplete { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Models/UpdateTodoItemModel.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Models
2 | {
3 | using System.ComponentModel.DataAnnotations;
4 |
5 | public class UpdateTodoItemModel
6 | {
7 | ///
8 | /// Gets or sets the name of this to do item.
9 | ///
10 | [Required(AllowEmptyStrings = false)]
11 | [MinLength(2)]
12 | [MaxLength(100)]
13 | public string Name { get; set; }
14 |
15 | ///
16 | /// Gets or sets whether this to do item has been completed.
17 | ///
18 | [Required]
19 | public bool? IsComplete { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.WebApi.UnitTests/ModuleInitializer.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi
2 | {
3 | using System.Runtime.CompilerServices;
4 |
5 | using VerifyTests;
6 |
7 | public static class ModuleInitializer
8 | {
9 | internal static readonly VerifySettings VerifySettings = new();
10 |
11 | [ModuleInitializer]
12 | public static void Initialize()
13 | {
14 | VerifierSettings.InitializePlugins();
15 | Recording.Start();
16 |
17 | VerifySettings.UseDirectory("VerifySnapshots");
18 | VerifySettings.ScrubEmptyLines();
19 | VerifySettings.ScrubInlineGuids();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.Services.UnitTests/ModuleInitializer.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services
2 | {
3 | using System.Runtime.CompilerServices;
4 |
5 | using VerifyTests;
6 |
7 | public static class ModuleInitializer
8 | {
9 | internal static readonly VerifySettings VerifySettings = new();
10 |
11 | [ModuleInitializer]
12 | public static void Initialize()
13 | {
14 | VerifierSettings.InitializePlugins();
15 | Recording.Start();
16 |
17 | VerifySettings.UseDirectory("VerifySnapshots");
18 | VerifySettings.ScrubEmptyLines();
19 | VerifySettings.ScrubInlineGuids();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/Security/GenerateJwtInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.Security
2 | {
3 | ///
4 | /// Contains the details needed to generate a JSON web token based on a user name and password.
5 | ///
6 | public sealed class GenerateJwtInfo
7 | {
8 | public string UserName { get; set; }
9 |
10 | // ReSharper disable once UnusedAutoPropertyAccessor.Global
11 | public string Password { get; set; }
12 |
13 | public string Audience { get; set; }
14 |
15 | public string Issuer { get; set; }
16 |
17 | public string Secret { get; set; }
18 |
19 | public string[] Scopes { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Todo.Services/TodoItemManagement/UpdateTodoItemInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Services.TodoItemManagement
2 | {
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Security.Principal;
5 |
6 | public class UpdateTodoItemInfo
7 | {
8 | [Required]
9 | [Range(1, long.MaxValue)]
10 | public long? Id { get; set; }
11 |
12 | [Required(AllowEmptyStrings = false)]
13 | [MinLength(2)]
14 | [MaxLength(100)]
15 | public string Name { get; set; }
16 |
17 | [Required]
18 | public bool? IsComplete { get; set; }
19 |
20 | [Required]
21 | public IPrincipal Owner { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.ApplicationFlows.IntegrationTests/VerifySnapshots/TransactionalBaseApplicationFlowTests.ExecuteAsync_WhenAllStepsSucceeds_MustSucceed.verified.txt:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | Id: {Scrubbed},
4 | Name: todo-item--Guid_1--#1,
5 | IsComplete: false,
6 | CreatedBy: test-user--Guid_2,
7 | CreatedOn: DateTime_1
8 | },
9 | {
10 | Id: {Scrubbed},
11 | Name: todo-item--Guid_1--#2,
12 | IsComplete: false,
13 | CreatedBy: test-user--Guid_2,
14 | CreatedOn: DateTime_2
15 | },
16 | {
17 | Id: {Scrubbed},
18 | Name: todo-item--Guid_1--#3,
19 | IsComplete: false,
20 | CreatedBy: test-user--Guid_2,
21 | CreatedOn: DateTime_3
22 | }
23 | ]
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.Telemetry.UnitTests/Serilog/ConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.Serilog
2 | {
3 | using System.IO;
4 | using System.Text;
5 |
6 | using Microsoft.Extensions.Configuration;
7 |
8 | internal static class ConfigurationExtensions
9 | {
10 | internal static IConfiguration ToConfiguration(this string jsonFragment)
11 | {
12 | using Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonFragment));
13 |
14 | ConfigurationBuilder configurationBuilder = new();
15 | configurationBuilder.AddJsonStream(stream);
16 | return configurationBuilder.Build();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Todo.Commons/Diagnostics/ActivitySources.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Commons.Diagnostics
2 | {
3 | using System.Diagnostics;
4 | using System.Reflection;
5 |
6 | public static class ActivitySources
7 | {
8 | private const string ActivitySourceName = "TodoWebApi";
9 |
10 | static ActivitySources()
11 | {
12 | TodoWebApi = new ActivitySource
13 | (
14 | name: ActivitySourceName,
15 | version: Assembly.GetEntryAssembly()!.GetCustomAttribute()!.InformationalVersion
16 | );
17 | }
18 |
19 | public static ActivitySource TodoWebApi { get; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=Staging_expectedStatusCode=Forbidden.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Forbidden,
7 | ReasonPhrase: Forbidden,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | }
14 | ],
15 | TrailingHeaders: [],
16 | RequestMessage: {
17 | Version: 1.1,
18 | Method: {
19 | Method: GET
20 | },
21 | RequestUri: http://localhost/api/configuration,
22 | Headers: []
23 | },
24 | IsSuccessStatusCode: false
25 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=DemoInAzure_expectedStatusCode=Forbidden.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Forbidden,
7 | ReasonPhrase: Forbidden,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | }
14 | ],
15 | TrailingHeaders: [],
16 | RequestMessage: {
17 | Version: 1.1,
18 | Method: {
19 | Method: GET
20 | },
21 | RequestUri: http://localhost/api/configuration,
22 | Headers: []
23 | },
24 | IsSuccessStatusCode: false
25 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=Production_expectedStatusCode=Forbidden.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Forbidden,
7 | ReasonPhrase: Forbidden,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | }
14 | ],
15 | TrailingHeaders: [],
16 | RequestMessage: {
17 | Version: 1.1,
18 | Method: {
19 | Method: GET
20 | },
21 | RequestUri: http://localhost/api/configuration,
22 | Headers: []
23 | },
24 | IsSuccessStatusCode: false
25 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.ApplicationFlows.IntegrationTests/ModuleInitializer.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows
2 | {
3 | using System.Runtime.CompilerServices;
4 |
5 | using VerifyTests;
6 |
7 | public static class ModuleInitializer
8 | {
9 | internal static readonly VerifySettings VerifySettings = new();
10 |
11 | [ModuleInitializer]
12 | public static void Initialize()
13 | {
14 | VerifierSettings.InitializePlugins();
15 | Recording.Start();
16 |
17 | VerifySettings.UseDirectory("VerifySnapshots");
18 | VerifySettings.ScrubEmptyLines();
19 | VerifySettings.ScrubInlineGuids();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=AcceptanceTests_expectedStatusCode=Forbidden.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Forbidden,
7 | ReasonPhrase: Forbidden,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | }
14 | ],
15 | TrailingHeaders: [],
16 | RequestMessage: {
17 | Version: 1.1,
18 | Method: {
19 | Method: GET
20 | },
21 | RequestUri: http://localhost/api/configuration,
22 | Headers: []
23 | },
24 | IsSuccessStatusCode: false
25 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=IntegrationTests_expectedStatusCode=Forbidden.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Forbidden,
7 | ReasonPhrase: Forbidden,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | }
14 | ],
15 | TrailingHeaders: [],
16 | RequestMessage: {
17 | Version: 1.1,
18 | Method: {
19 | Method: GET
20 | },
21 | RequestUri: http://localhost/api/configuration,
22 | Headers: []
23 | },
24 | IsSuccessStatusCode: false
25 | }
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/ExceptionHandling/Configuration/ExceptionHandlingOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.ExceptionHandling.Configuration
2 | {
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | ///
6 | /// Contains exception handling related configuration options.
7 | ///
8 | public class ExceptionHandlingOptions
9 | {
10 | ///
11 | /// Gets or sets whether to include details when converting an instance of class
12 | /// to an instance of class.
13 | ///
14 | // ReSharper disable once UnusedAutoPropertyAccessor.Global
15 | public bool IncludeDetails { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.DeleteAsync_UsingNewlyCreatedTodoItem_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: NoContent,
7 | ReasonPhrase: No Content,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | }
14 | ],
15 | TrailingHeaders: [],
16 | RequestMessage: {
17 | Version: 1.1,
18 | Method: {
19 | Method: DELETE
20 | },
21 | RequestUri: {Scrubbed},
22 | Headers: [
23 | {
24 | Authorization: [
25 | Bearer
26 | ]
27 | }
28 | ]
29 | },
30 | IsSuccessStatusCode: true
31 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.DeleteAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Unauthorized,
7 | ReasonPhrase: Unauthorized,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | },
14 | {
15 | WWW-Authenticate: [
16 | Bearer
17 | ]
18 | }
19 | ],
20 | TrailingHeaders: [],
21 | RequestMessage: {
22 | Version: 1.1,
23 | Method: {
24 | Method: DELETE
25 | },
26 | RequestUri: http://localhost/api/todo/2147483647,
27 | Headers: []
28 | },
29 | IsSuccessStatusCode: false
30 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.GetByIdAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Unauthorized,
7 | ReasonPhrase: Unauthorized,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | },
14 | {
15 | WWW-Authenticate: [
16 | Bearer
17 | ]
18 | }
19 | ],
20 | TrailingHeaders: [],
21 | RequestMessage: {
22 | Version: 1.1,
23 | Method: {
24 | Method: GET
25 | },
26 | RequestUri: http://localhost/api/todo/2147483647,
27 | Headers: []
28 | },
29 | IsSuccessStatusCode: false
30 | }
--------------------------------------------------------------------------------
/.sonarlint/aspnet-core-logging_secrets_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "sonarlint.rules": {
3 | "secrets:S6338": {
4 | "level": "On",
5 | "severity": "Blocker"
6 | },
7 | "secrets:S6337": {
8 | "level": "On",
9 | "severity": "Blocker"
10 | },
11 | "secrets:S6290": {
12 | "level": "On",
13 | "severity": "Blocker"
14 | },
15 | "secrets:S6334": {
16 | "level": "On",
17 | "severity": "Blocker"
18 | },
19 | "secrets:S6336": {
20 | "level": "On",
21 | "severity": "Blocker"
22 | },
23 | "secrets:S6335": {
24 | "level": "On",
25 | "severity": "Blocker"
26 | },
27 | "secrets:S6292": {
28 | "level": "On",
29 | "severity": "Blocker"
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/HealthCheckControllerTests.GetHealthReportAsync_WhenRequestIsNotAuthorized_ReturnsUnauthorizedHttpStatusCode.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Unauthorized,
7 | ReasonPhrase: Unauthorized,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | },
14 | {
15 | WWW-Authenticate: [
16 | Bearer
17 | ]
18 | }
19 | ],
20 | TrailingHeaders: [],
21 | RequestMessage: {
22 | Version: 1.1,
23 | Method: {
24 | Method: GET
25 | },
26 | RequestUri: http://localhost/api/health,
27 | Headers: []
28 | },
29 | IsSuccessStatusCode: false
30 | }
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Models/TodoItemQueryModel.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable MemberCanBePrivate.Global
2 | // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
3 |
4 | namespace Todo.WebApi.Models
5 | {
6 | using System.ComponentModel.DataAnnotations;
7 |
8 | public class TodoItemQueryModel
9 | {
10 | public long? Id { get; set; }
11 |
12 | public string NamePattern { get; set; }
13 |
14 | public bool? IsComplete { get; set; }
15 |
16 | [Range(1, 1000)]
17 | public int PageSize { get; set; } = 25;
18 |
19 | [Range(0, int.MaxValue)]
20 | public int PageIndex { get; set; }
21 |
22 | public string SortBy { get; set; }
23 |
24 | public bool? IsSortAscending { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Authorization/HasScopeRequirement.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Authorization
2 | {
3 | using System;
4 |
5 | using Microsoft.AspNetCore.Authorization;
6 |
7 | ///
8 | /// Based on: https://auth0.com/docs/quickstart/backend/aspnet-core-webapi#validate-scopes.
9 | ///
10 | public class HasScopeRequirement : IAuthorizationRequirement
11 | {
12 | public string Issuer { get; }
13 |
14 | public string Scope { get; }
15 |
16 | public HasScopeRequirement(string scope, string issuer)
17 | {
18 | Scope = scope ?? throw new ArgumentNullException(nameof(scope));
19 | Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/.artifactignore:
--------------------------------------------------------------------------------
1 | # Ensure only specific items are published as artifacts when running an Azure Pipeline.
2 | # See more about this file here: https://learn.microsoft.com/en-us/azure/devops/artifacts/reference/artifactignore?view=azure-devops.
3 | **/*
4 |
5 | # Publish test results
6 | !**/TestResults/TestResults.xml
7 | !**/Tests/AcceptanceTests/**/TestExecution.json
8 | !**/Tests/AcceptanceTests/LivingDoc.html
9 |
10 | # Publish code coverage results
11 | !**/coverage.opencover.xml
12 | !**/Tests/.CodeCoverageReport/Cobertura.xml
13 |
14 | # Publish application logs generated while running integration tests
15 | !**/Logs/*.log
16 |
17 | # Publish logs generated when starting the Docker Compose services
18 | # used by integration tests
19 | !**/Tests/.ComposeService-Logs/**/*.log
20 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.GetByQueryAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Unauthorized,
7 | ReasonPhrase: Unauthorized,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | },
14 | {
15 | WWW-Authenticate: [
16 | Bearer
17 | ]
18 | }
19 | ],
20 | TrailingHeaders: [],
21 | RequestMessage: {
22 | Version: 1.1,
23 | Method: {
24 | Method: GET
25 | },
26 | RequestUri: http://localhost/api/todo?PageIndex=0&PageSize=5&SortBy=CreatedOn&IsSortAscending=False,
27 | Headers: []
28 | },
29 | IsSuccessStatusCode: false
30 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/ConfigurationControllerTests.GetConfigurationDebugView_WhenCalled_MustBehaveAsExpected_environmentName=Development_expectedStatusCode=OK.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: [
5 | {
6 | Content-Type: [
7 | text/plain; charset=utf-8
8 | ]
9 | }
10 | ]
11 | },
12 | StatusCode: OK,
13 | ReasonPhrase: OK,
14 | Headers: [
15 | {
16 | ConversationId: [
17 | Guid_1
18 | ]
19 | }
20 | ],
21 | TrailingHeaders: [],
22 | RequestMessage: {
23 | Version: 1.1,
24 | Method: {
25 | Method: GET
26 | },
27 | RequestUri: http://localhost/api/configuration,
28 | Headers: []
29 | },
30 | IsSuccessStatusCode: true
31 | }
--------------------------------------------------------------------------------
/Sources/Todo.Persistence/Migrations/20200124210915_AddIndexForNameColumnInsideTodoItemsTable.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Persistence.Migrations
2 | {
3 | using Microsoft.EntityFrameworkCore.Migrations;
4 |
5 | public partial class AddIndexForNameColumnInsideTodoItemsTable : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.CreateIndex(
10 | name: "IX_TodoItems_Name",
11 | table: "TodoItems",
12 | column: "Name");
13 | }
14 |
15 | protected override void Down(MigrationBuilder migrationBuilder)
16 | {
17 | migrationBuilder.DropIndex(
18 | name: "IX_TodoItems_Name",
19 | table: "TodoItems");
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TransactionOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows
2 | {
3 | using System;
4 | using System.Transactions;
5 |
6 | ///
7 | /// Configures the behavior of the transactions occurring inside a particular application flow.
8 | ///
9 | // ReSharper disable once ClassNeverInstantiated.Global
10 | public class TransactionOptions
11 | {
12 | ///
13 | /// Gets or sets the transaction isolation level.
14 | ///
15 | // ReSharper disable once UnusedAutoPropertyAccessor.Global
16 | public IsolationLevel IsolationLevel { get; set; }
17 |
18 | ///
19 | /// Gets or sets the transaction timeout.
20 | ///
21 | public TimeSpan Timeout { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Todo.Persistence/Migrations/20200124211005_AddIndexForCreatedByColumnInsideTodoItemsTable.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Persistence.Migrations
2 | {
3 | using Microsoft.EntityFrameworkCore.Migrations;
4 |
5 | public partial class AddIndexForCreatedByColumnInsideTodoItemsTable : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.CreateIndex(
10 | name: "IX_TodoItems_CreatedBy",
11 | table: "TodoItems",
12 | column: "CreatedBy");
13 | }
14 |
15 | protected override void Down(MigrationBuilder migrationBuilder)
16 | {
17 | migrationBuilder.DropIndex(
18 | name: "IX_TodoItems_CreatedBy",
19 | table: "TodoItems");
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.GetByQueryAsync_UsingDefaults_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: [
5 | {
6 | Content-Type: [
7 | application/json; charset=utf-8
8 | ]
9 | }
10 | ]
11 | },
12 | StatusCode: OK,
13 | ReasonPhrase: OK,
14 | Headers: [
15 | {
16 | ConversationId: [
17 | Guid_1
18 | ]
19 | }
20 | ],
21 | TrailingHeaders: [],
22 | RequestMessage: {
23 | Version: 1.1,
24 | Method: {
25 | Method: GET
26 | },
27 | RequestUri: {Scrubbed},
28 | Headers: [
29 | {
30 | Authorization: [
31 | Bearer
32 | ]
33 | }
34 | ]
35 | },
36 | IsSuccessStatusCode: true
37 | }
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.GetByIdAsync_UsingNewlyCreatedItem_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: [
5 | {
6 | Content-Type: [
7 | application/json; charset=utf-8
8 | ]
9 | }
10 | ]
11 | },
12 | StatusCode: OK,
13 | ReasonPhrase: OK,
14 | Headers: [
15 | {
16 | ConversationId: [
17 | Guid_1
18 | ]
19 | }
20 | ],
21 | TrailingHeaders: [],
22 | RequestMessage: {
23 | Version: 1.1,
24 | Method: {
25 | Method: GET
26 | },
27 | RequestUri: {Scrubbed},
28 | Headers: [
29 | {
30 | Authorization: [
31 | Bearer
32 | ]
33 | }
34 | ]
35 | },
36 | IsSuccessStatusCode: true
37 | }
--------------------------------------------------------------------------------
/Sources/Todo.Persistence/Migrations/20200515210035_AddSupportForOptimisticLockingToTodoTable.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Persistence.Migrations
2 | {
3 | using Microsoft.EntityFrameworkCore.Migrations;
4 |
5 | public partial class AddSupportForOptimisticLockingToTodoTable : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.AddColumn(
10 | name: "xmin",
11 | table: "TodoItems",
12 | type: "xid",
13 | nullable: false,
14 | defaultValue: 0u);
15 | }
16 |
17 | protected override void Down(MigrationBuilder migrationBuilder)
18 | {
19 | migrationBuilder.DropColumn(
20 | name: "xmin",
21 | table: "TodoItems");
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/Todo.Persistence/Migrations/20221117205342_AddVersionColumnToTheTodoItemsTable.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Persistence.Migrations
2 | {
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | using Microsoft.EntityFrameworkCore.Migrations;
6 |
7 | ///
8 | public partial class AddVersionColumnToTheTodoItemsTable : Migration
9 | {
10 | ///
11 | protected override void Up(MigrationBuilder migrationBuilder)
12 | {
13 |
14 | }
15 |
16 | ///
17 | [SuppressMessage("Critical Code Smell", "S1186:Methods should not be empty",
18 | Justification = "The newly added Version property to the TodoItem entity will not result in database changes")]
19 | protected override void Down(MigrationBuilder migrationBuilder)
20 | {
21 |
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/HealthCheckControllerTests.GetHealthReportAsync_UsingValidInput_ReturnsExpectedHealthReport.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: [
5 | {
6 | Content-Type: [
7 | application/json; charset=utf-8
8 | ]
9 | }
10 | ]
11 | },
12 | StatusCode: OK,
13 | ReasonPhrase: OK,
14 | Headers: [
15 | {
16 | ConversationId: [
17 | Guid_1
18 | ]
19 | }
20 | ],
21 | TrailingHeaders: [],
22 | RequestMessage: {
23 | Version: 1.1,
24 | Method: {
25 | Method: GET
26 | },
27 | RequestUri: http://localhost/api/health,
28 | Headers: [
29 | {
30 | Authorization: [
31 | Bearer
32 | ]
33 | }
34 | ]
35 | },
36 | IsSuccessStatusCode: true
37 | }
--------------------------------------------------------------------------------
/Sources/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 | false
11 | false
12 | ..\..\.sonarlint\aspnet-core-loggingCSharp.ruleset
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.GetByIdAsync_UsingNonExistingId_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: [
5 | {
6 | Content-Type: [
7 | application/problem+json; charset=utf-8
8 | ]
9 | }
10 | ]
11 | },
12 | StatusCode: NotFound,
13 | ReasonPhrase: Not Found,
14 | Headers: [
15 | {
16 | ConversationId: [
17 | Guid_1
18 | ]
19 | }
20 | ],
21 | TrailingHeaders: [],
22 | RequestMessage: {
23 | Version: 1.1,
24 | Method: {
25 | Method: GET
26 | },
27 | RequestUri: http://localhost/api/todo/-9223372036854775808,
28 | Headers: [
29 | {
30 | Authorization: [
31 | Bearer
32 | ]
33 | }
34 | ]
35 | },
36 | IsSuccessStatusCode: false
37 | }
--------------------------------------------------------------------------------
/Tests/ArchitectureTests/Todo.ArchitectureTests/Todo.ArchitectureTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | Library
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/Serilog/SerilogConstants.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.Serilog
2 | {
3 | ///
4 | /// Contains constants related to Serilog configuration.
5 | ///
6 | internal static class SerilogConstants
7 | {
8 | ///
9 | /// Contains constants related to Serilog configuration sections.
10 | ///
11 | internal static class SectionNames
12 | {
13 | internal const string Using = "Serilog:Using";
14 | }
15 |
16 | ///
17 | /// Contains constants representing the Serilog sinks used by this application.
18 | ///
19 | internal static class SinkShortNames
20 | {
21 | internal const string ApplicationInsights = "Serilog.Sinks.ApplicationInsights";
22 | internal const string File = "Serilog.Sinks.File";
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/Todo.Commons/Constants/Logging.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Commons.Constants
2 | {
3 | ///
4 | /// Contains constants related to logging various events generated by this application and its host.
5 | ///
6 | public static class Logging
7 | {
8 | ///
9 | /// Represents the name of the folder where log files will be created.
10 | ///
11 | public const string LogsHomeEnvironmentVariable = "LOGS_HOME";
12 |
13 | ///
14 | /// Represents the identifier of the conversation grouping several events related to the same operation.
15 | ///
16 | public const string ConversationId = "ConversationId";
17 |
18 | ///
19 | /// Represents the name of an application flow.
20 | ///
21 | public const string ApplicationFlowName = "ApplicationFlowName";
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Todo.Commons/Constants/ConnectionStrings.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Commons.Constants
2 | {
3 | ///
4 | /// Contains constants related to the connection strings used by this application.
5 | ///
6 | public static class ConnectionStrings
7 | {
8 | ///
9 | /// Represents the name of the connection string to be used when running integration tests.
10 | ///
11 | public const string UsedByIntegrationTests = "IntegrationTests";
12 |
13 | ///
14 | /// Represents the name of the connection string to be used when running acceptance tests.
15 | ///
16 | public const string UsedByAcceptanceTests = "AcceptanceTests";
17 |
18 | ///
19 | /// Represents the name of the connection string to be used when running the application.
20 | ///
21 | public const string UsedByApplication = "Application";
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/IApplicationFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows
2 | {
3 | using System.Security.Principal;
4 | using System.Threading.Tasks;
5 |
6 | ///
7 | /// An application flow implements a specific feature needed for business or technical reasons.
8 | ///
9 | /// The type of the input needed to execute this flow.
10 | /// The type of the outcome of this flow.
11 | public interface IApplicationFlow
12 | {
13 | ///
14 | /// Executes this flow.
15 | ///
16 | /// The input needed to execute this flow.
17 | /// The user who initiated executing this flow.
18 | /// An instance of type.
19 | Task ExecuteAsync(TInput input, IPrincipal flowInitiator);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.CreateAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Unauthorized,
7 | ReasonPhrase: Unauthorized,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | },
14 | {
15 | WWW-Authenticate: [
16 | Bearer
17 | ]
18 | }
19 | ],
20 | TrailingHeaders: [],
21 | RequestMessage: {
22 | Version: 1.1,
23 | Content: {
24 | Headers: [
25 | {
26 | Content-Type: [
27 | application/json; charset=utf-8
28 | ]
29 | },
30 | {
31 | Content-Length: [
32 | 31
33 | ]
34 | }
35 | ]
36 | },
37 | Method: {
38 | Method: POST
39 | },
40 | RequestUri: http://localhost/api/todo,
41 | Headers: []
42 | },
43 | IsSuccessStatusCode: false
44 | }
--------------------------------------------------------------------------------
/Sources/Todo.Commons/StartupLogic/HostExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Commons.StartupLogic
2 | {
3 | using System.Threading.Tasks;
4 |
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Hosting;
7 |
8 | ///
9 | /// Contains extension methods applicable to objects.
10 | ///
11 | public static class HostExtensions
12 | {
13 | ///
14 | /// Runs application startup logic (e.g., database migrations), then the host.
15 | ///
16 | /// The instance to run.
17 | public static async Task RunWithTasksAsync(this IHost host)
18 | {
19 | await using AsyncServiceScope asyncServiceScope = host.Services.CreateAsyncScope();
20 | await asyncServiceScope.ServiceProvider.GetRequiredService().ExecuteAsync();
21 | await host.RunAsync();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/IntegrationTests/Todo.WebApi.IntegrationTests/Controllers/VerifySnapshots/TodoControllerTests.UpdateAsync_WhenRequestIsNotAuthorized_ReturnsExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Version: 1.1,
3 | Content: {
4 | Headers: []
5 | },
6 | StatusCode: Unauthorized,
7 | ReasonPhrase: Unauthorized,
8 | Headers: [
9 | {
10 | ConversationId: [
11 | Guid_1
12 | ]
13 | },
14 | {
15 | WWW-Authenticate: [
16 | Bearer
17 | ]
18 | }
19 | ],
20 | TrailingHeaders: [],
21 | RequestMessage: {
22 | Version: 1.1,
23 | Content: {
24 | Headers: [
25 | {
26 | Content-Type: [
27 | application/json; charset=utf-8
28 | ]
29 | },
30 | {
31 | Content-Length: [
32 | 31
33 | ]
34 | }
35 | ]
36 | },
37 | Method: {
38 | Method: PUT
39 | },
40 | RequestUri: http://localhost/api/todo/2147483647,
41 | Headers: []
42 | },
43 | IsSuccessStatusCode: false
44 | }
--------------------------------------------------------------------------------
/Tests/Infrastructure/Todo.WebApi.TestInfrastructure/Todo.WebApi.TestInfrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | Library
5 | Todo.WebApi.TestInfrastructure
6 |
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | false
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:57747",
8 | "sslPort": 44353
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "Project",
14 | "launchBrowser": false,
15 | "launchUrl": "api/todo",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "Development": {
21 | "commandName": "Project",
22 | "launchBrowser": false,
23 | "launchUrl": "api/todo",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | },
27 | "applicationUrl": "https://localhost:5001;http://localhost:5000"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Visual Studio artifacts
2 | .vs/
3 | bin/
4 | obj/
5 |
6 | # Visual Studio Code artifacts
7 | .vscode/
8 | /Tests/Infrastructure/TestInfrastructure/Properties/launchSettings.json
9 | /Tests/Infrastructure/TestInfrastructure/TestInfrastructure.csproj.user
10 |
11 | # Rider IDE artifacts
12 | .idea/
13 | mono_crash.mem.*
14 | Todo.sln.DotSettings.user
15 |
16 | # ReSharper artifacts
17 | *.sln.DotSettings.user
18 |
19 | # Application logs
20 | /**/Logs/
21 |
22 | # Tests
23 | /Tests/**/.TestResults/
24 | /Tests/**/TestResults/
25 | LivingDoc.html
26 |
27 | # Code coverage
28 | /Tests/**/.CodeCoverageResults/
29 | /Tests/**/coverage.cobertura.xml
30 | /Tests/**/coverage.opencover.xml
31 | /Tests/**/coverage.json
32 |
33 | # SonarQube scanner artifacts
34 | .sonarqube/
35 |
36 | # File containing environment variables used by docker-compose
37 | *.env
38 |
39 | # Report generated by dotnet-format tool
40 | ./dotnet-format-report.json
41 |
42 | # Log file generated by dotnet-format tool
43 | /formatDiagnosticLog.binlog
44 |
45 | # Verify
46 | *.received.*
47 | *.received/
48 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/Security/GenerateJwtFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.Security
2 | {
3 | using System;
4 | using System.Security.Principal;
5 | using System.Threading.Tasks;
6 |
7 | using Microsoft.Extensions.Logging;
8 |
9 | using Todo.Services.Security;
10 |
11 | ///
12 | /// An implementation.
13 | ///
14 | public class GenerateJwtFlow : NonTransactionalBaseApplicationFlow, IGenerateJwtFlow
15 | {
16 | private readonly IJwtService jwtService;
17 |
18 | public GenerateJwtFlow(IJwtService jwtService, ILogger logger) :
19 | base("Security/GenerateJwt", logger)
20 | {
21 | this.jwtService = jwtService ?? throw new ArgumentNullException(nameof(jwtService));
22 | }
23 |
24 | protected override async Task ExecuteFlowStepsAsync(GenerateJwtInfo input, IPrincipal flowInitiator)
25 | {
26 | return await jwtService.GenerateJwtAsync(input);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright 2018-2025 Bogdan Marian
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/Http/HttpLoggingMiddlewareExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.Http
2 | {
3 | using System;
4 |
5 | using Microsoft.AspNetCore.Builder;
6 |
7 | ///
8 | /// Contains extension methods applicable to instances.
9 | ///
10 | public static class LoggingMiddlewareExtensions
11 | {
12 | ///
13 | /// Adds middleware for logging the current object.
14 | ///
15 | ///
16 | ///
17 | // ReSharper disable once UnusedMethodReturnValue.Global
18 | internal static IApplicationBuilder UseHttpLogging(this IApplicationBuilder applicationBuilder)
19 | {
20 | if (applicationBuilder == null)
21 | {
22 | throw new ArgumentNullException(nameof(applicationBuilder));
23 | }
24 |
25 | applicationBuilder.UseMiddleware();
26 | return applicationBuilder;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Tests/AcceptanceTests/Todo.WebApi.AcceptanceTests/Infrastructure/TcpPortProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace Todo.WebApi.AcceptanceTests.Infrastructure
4 | {
5 | using System.Net;
6 | using System.Net.Sockets;
7 |
8 | public class TcpPortProvider
9 | {
10 | private static readonly int AvailableTcpPort;
11 |
12 | static TcpPortProvider()
13 | {
14 | AvailableTcpPort = InternalGetAvailableTcpPort();
15 | }
16 |
17 | [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Ensure only one TCP port is opened while executing acceptance tests")]
18 | public int GetAvailableTcpPort()
19 | {
20 | return AvailableTcpPort;
21 | }
22 |
23 | private static int InternalGetAvailableTcpPort()
24 | {
25 | using TcpListener tcpListener = new(IPAddress.Loopback, 0);
26 | tcpListener.Start();
27 |
28 | int availableTcpPort = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
29 |
30 | tcpListener.Stop();
31 | return availableTcpPort;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/Http/ConversationIdProviderMiddlewareExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.Http
2 | {
3 | using System;
4 |
5 | using Microsoft.AspNetCore.Builder;
6 |
7 | ///
8 | /// Contains extension methods applicable to instances.
9 | ///
10 | public static class ConversationIdProviderMiddlewareExtensions
11 | {
12 | ///
13 | /// Adds middleware for providing a conversation ID to each HTTP request.
14 | ///
15 | /// Configures ASP.NET Core request processing pipeline.
16 | /// The given instance.
17 | public static IApplicationBuilder UseConversationId(this IApplicationBuilder applicationBuilder)
18 | {
19 | if (applicationBuilder == null)
20 | {
21 | throw new ArgumentNullException(nameof(applicationBuilder));
22 | }
23 |
24 | applicationBuilder.UseMiddleware();
25 | return applicationBuilder;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Build/db4tests/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | db4it:
3 | image: "${db_docker_image}"
4 | healthcheck:
5 | test: >
6 | pg_isready \
7 | --host=localhost \
8 | --port=5432 \
9 | --dbname=${db_name} \
10 | --username=${db_username}
11 | interval: "2s"
12 | timeout: "5s"
13 | retries: 5
14 | start_period: "1s"
15 | stdin_open: false
16 | tty: false
17 | environment:
18 | POSTGRES_DB: "${db_name}"
19 | POSTGRES_USER: "${db_username}"
20 | POSTGRES_PASSWORD: "${db_password}"
21 | ports:
22 | - "5432"
23 |
24 | db4at:
25 | image: "${db_docker_image}"
26 | healthcheck:
27 | test: >
28 | pg_isready \
29 | --host=localhost \
30 | --port=5432 \
31 | --dbname=${db_name} \
32 | --username=${db_username}
33 | interval: "2s"
34 | timeout: "5s"
35 | retries: 5
36 | start_period: "1s"
37 | stdin_open: false
38 | tty: false
39 | environment:
40 | POSTGRES_DB: "${db_name}"
41 | POSTGRES_USER: "${db_username}"
42 | POSTGRES_PASSWORD: "${db_password}"
43 | ports:
44 | - "5432"
45 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TodoItems/AddTodoItemFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.TodoItems
2 | {
3 | using System;
4 | using System.Security.Principal;
5 | using System.Threading.Tasks;
6 |
7 | using Microsoft.Extensions.Logging;
8 |
9 | using Services.TodoItemManagement;
10 |
11 | ///
12 | /// An implementation.
13 | ///
14 | public class AddTodoItemFlow : TransactionalBaseApplicationFlow, IAddTodoItemFlow
15 | {
16 | private readonly ITodoItemService todoItemService;
17 |
18 | public AddTodoItemFlow(ITodoItemService todoItemService, ApplicationFlowOptions applicationFlowOptions, ILogger logger)
19 | : base("TodoItem/Add", applicationFlowOptions, logger)
20 | {
21 | this.todoItemService = todoItemService ?? throw new ArgumentNullException(nameof(todoItemService));
22 | }
23 |
24 | protected override async Task ExecuteFlowStepsAsync(NewTodoItemInfo input, IPrincipal flowInitiator)
25 | {
26 | input.Owner = flowInitiator;
27 |
28 | return await todoItemService.AddAsync(input);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Tests/UnitTests/Todo.Services.UnitTests/Security/VerifySnapshots/JwtServiceTests.GenerateJwtAsync_WhenUsingValidInput_MustReturnExpectedResult.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Audiences: [
3 | test-audience
4 | ],
5 | Claims: [
6 | {
7 | nameid: c29tZS10ZXN0LXVzZXI=
8 | },
9 | {
10 | scope: resource1 resource2
11 | },
12 | {
13 | nbf: Scrubbed
14 | },
15 | {
16 | exp: Scrubbed
17 | },
18 | {
19 | iat: Scrubbed
20 | },
21 | {
22 | iss: test
23 | },
24 | {
25 | aud: test-audience
26 | }
27 | ],
28 | EncodedHeader: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,
29 | EncodedPayload: {Scrubbed},
30 | Header: {
31 | alg: HS256,
32 | typ: JWT
33 | },
34 | Issuer: test,
35 | Payload: {
36 | aud: test-audience,
37 | exp: {Scrubbed},
38 | iat: {Scrubbed},
39 | iss: test,
40 | nameid: c29tZS10ZXN0LXVzZXI=,
41 | nbf: {Scrubbed},
42 | scope: resource1 resource2
43 | },
44 | RawData: {Scrubbed},
45 | RawHeader: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,
46 | RawPayload: {Scrubbed},
47 | RawSignature: {Scrubbed},
48 | SignatureAlgorithm: HS256,
49 | ValidFrom: DateTime_1,
50 | ValidTo: DateTime_2,
51 | IssuedAt: DateTime_1
52 | }
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/DependencyInjection/TelemetryModule.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.DependencyInjection
2 | {
3 | using Autofac;
4 |
5 | using Commons.StartupLogic;
6 |
7 | using Http;
8 |
9 | using Serilog;
10 |
11 | ///
12 | /// Configures telemetry related services used by this application.
13 | ///
14 | public class TelemetryModule : Module
15 | {
16 | ///
17 | /// Gets or sets whether HTTP requests and their responses will be logged.
18 | ///
19 | public bool EnableHttpLogging { get; set; }
20 |
21 | protected override void Load(ContainerBuilder builder)
22 | {
23 | if (EnableHttpLogging)
24 | {
25 | builder
26 | .RegisterType()
27 | .AsSelf()
28 | .As()
29 | .As()
30 | .SingleInstance();
31 | }
32 |
33 | builder
34 | .RegisterType()
35 | .As()
36 | .SingleInstance();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Todo.WebApi/Authorization/HasScopeHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.WebApi.Authorization
2 | {
3 | using System;
4 | using System.Security.Claims;
5 | using System.Threading.Tasks;
6 |
7 | using Microsoft.AspNetCore.Authorization;
8 |
9 | ///
10 | /// Based on: https://auth0.com/docs/quickstart/backend/aspnet-core-webapi#validate-scopes.
11 | ///
12 | public class HasScopeHandler : AuthorizationHandler
13 | {
14 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
15 | {
16 | Claim scopeClaim = context.User.FindFirst(claim => claim.Type == "scope" && claim.Issuer == requirement.Issuer);
17 |
18 | if (scopeClaim != null)
19 | {
20 | string[] scopes = scopeClaim.Value.Split(separator: ' ');
21 |
22 | if (Array.Exists(scopes, scope => scope == requirement.Scope))
23 | {
24 | context.Succeed(requirement);
25 | return Task.CompletedTask;
26 | }
27 | }
28 |
29 | context.Fail();
30 | return Task.CompletedTask;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/Todo.Telemetry/Http/IHttpObjectConverter.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.Telemetry.Http
2 | {
3 | using System.Threading.Tasks;
4 |
5 | using Microsoft.AspNetCore.Http;
6 |
7 | ///
8 | /// Converts HTTP objects to strings intended to be logged using
9 | /// method.
10 | ///
11 | public interface IHttpObjectConverter
12 | {
13 | ///
14 | /// Converts the given to a string.
15 | ///
16 | /// The instance to be converted.
17 | /// The string representation of an instance.
18 | Task ToLogMessageAsync(HttpRequest httpRequest);
19 |
20 | ///
21 | /// Converts the given to a string.
22 | ///
23 | /// The instance to be converted.
24 | /// The string representation of an instance.
25 | Task ToLogMessageAsync(HttpResponse httpResponse);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Todo.ApplicationFlows/TodoItems/UpdateTodoItemFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Todo.ApplicationFlows.TodoItems
2 | {
3 | using System;
4 | using System.Security.Principal;
5 | using System.Threading.Tasks;
6 |
7 | using Microsoft.Extensions.Logging;
8 |
9 | using Services.TodoItemManagement;
10 |
11 | ///
12 | /// An implementation.
13 | ///
14 | public class UpdateTodoItemFlow : TransactionalBaseApplicationFlow, IUpdateTodoItemFlow
15 | {
16 | private readonly ITodoItemService todoItemService;
17 |
18 | public UpdateTodoItemFlow(ITodoItemService todoItemService, ApplicationFlowOptions applicationFlowOptions, ILogger logger)
19 | : base("TodoItem/Update", applicationFlowOptions, logger)
20 | {
21 | this.todoItemService = todoItemService ?? throw new ArgumentNullException(nameof(todoItemService));
22 | }
23 |
24 | protected override async Task