diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index add242ca..be3a67c0 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -10,7 +10,6 @@ jobs:
with:
build_path: "./templates/Full"
sonar_project: "hexagonal-solution-template-full"
- test_no_build: false
unit_test_project_path: "./templates/Full/tests/UnitTests/"
dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}"
domain_stryker_config_path: "./templates/Full/tests/UnitTests/stryker-config-domain.json"
@@ -31,12 +30,22 @@ jobs:
secrets:
sonar_token: ${{ secrets.SONAR_TOKEN }}
+ validate-contracts-template:
+ name: Validate Contracts Template
+ uses: gpreviatti/github-actions-templates/.github/workflows/dotnet-validate.yml@v1
+ with:
+ build_path: "./templates/Contracts"
+ dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}"
+ unit_test_project_path: "./templates/Contracts/tests/UnitTests/"
+ default_stryker_config_path: "./templates/Contracts/tests/UnitTests/stryker-config.json"
+ sonar_validation: false
+
pack-and-publish:
name: Pack and Publish Template
- needs: [validate-template-full, validate-template-bff]
+ needs: [validate-template-full, validate-template-bff, validate-contracts-template]
uses: gpreviatti/github-actions-templates/.github/workflows/dotnet-pack.yml@v1
with:
dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}"
- package_version: "10.1.0"
+ package_version: "10.2.0"
secrets:
nuget_api_key: ${{ secrets.NUGET_API_KEY }}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 5b0d7898..6c00532a 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -11,7 +11,6 @@ jobs:
with:
build_path: "./templates/Full"
sonar_project: "hexagonal-solution-template-full"
- test_no_build: false
unit_test_project_path: "./templates/Full/tests/UnitTests/"
dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}"
domain_stryker_config_path: "./templates/Full/tests/UnitTests/stryker-config-domain.json"
@@ -28,4 +27,14 @@ jobs:
build_path: "./templates/Bff"
dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}"
integration_test_project_path: "./templates/Bff/tests/IntegrationTests"
- docker_compose_file_path: "./templates/Bff/docker-compose.yml"
\ No newline at end of file
+ docker_compose_file_path: "./templates/Bff/docker-compose.yml"
+
+ validate-contracts-template:
+ name: Validate Contracts Template
+ uses: gpreviatti/github-actions-templates/.github/workflows/dotnet-validate.yml@v1
+ with:
+ build_path: "./templates/Contracts"
+ dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}"
+ unit_test_project_path: "./templates/Contracts/tests/UnitTests/"
+ default_stryker_config_path: "./templates/Contracts/tests/UnitTests/stryker-config.json"
+ sonar_validation: false
\ No newline at end of file
diff --git a/GPreviatti.Template.Hexagonal.Solution.csproj b/GPreviatti.Template.Hexagonal.Solution.csproj
index 844afca5..98a407e4 100644
--- a/GPreviatti.Template.Hexagonal.Solution.csproj
+++ b/GPreviatti.Template.Hexagonal.Solution.csproj
@@ -18,14 +18,11 @@
git://github.com/gpreviatti/hexagonal-solution-template
true
- - Add new new template with BFF architecture
- - Remove Repository pattern since it's not needed with modern EF Core and adds unnecessary complexity
- - Update dependencies to latest versions
- - Update GitHub Actions workflows to test and analyze both templates
- - Improve OpenTelemetry configuration and add more metrics and traces
- - Remove Aspire Dashboard
- - Add Grafana, Prometheus, Tempo and Loki configuration with Docker Compose for easier local development and testing of observability features
- - Change DB to PostgreSQL since it's more widely used in production than SQL Server and works better with Docker
+ - Add new Contracts-only template (`hexagonal-solution-contracts`) with shared request/response contracts, DTOs and gRPC protobuf definitions
+ - Add unit tests in the Contracts template to validate contract instantiation and default values
+ - Add dedicated Contracts template solution file and template-level README
+ - Update root README with usage/help commands and guidance for the new Contracts template
+ - Optimize Contracts template package management by removing unused package versions
diff --git a/Readme.md b/Readme.md
index ab25827d..f110e267 100644
--- a/Readme.md
+++ b/Readme.md
@@ -6,6 +6,12 @@
This is a dotnet solution template from projects based on hexagonal architecture and best practices
+## Available templates
+
+- `hexagonal-solution-full`: Full hexagonal architecture template (Domain, Application, Infrastructure, WebApp, tests and load tests).
+- `hexagonal-solution-bff`: Backend-for-Frontend focused template with HTTP/gRPC integration patterns and integration/load tests.
+- `hexagonal-solution-contracts`: Contracts-only template for shared request/response models, DTOs, gRPC protobuf definitions and unit tests.
+
## Used Technologies
- FluentValidation
@@ -15,6 +21,7 @@ This is a dotnet solution template from projects based on hexagonal architecture
- Xunit
- Moq
- AutoFixture
+- gRPC / Protobuf
- Docker and Docker Compose
- K6
- Stryker
@@ -46,6 +53,14 @@ To create only the BFF template you can use the following command
dotnet new hexagonal-solution-bff -n HexagonalSolution
```
+To create only the Contracts template you can use the following command
+
+```bash
+dotnet new hexagonal-solution-contracts -n HexagonalSolution
+```
+
+The Contracts template is recommended when you want a lightweight shared library for API and messaging contracts that can be reused across services.
+
If you had any doubts about the existing parameters you can also use -h to get more information
```bash
@@ -58,6 +73,12 @@ or
dotnet new hexagonal-solution-bff -h
```
+or
+
+```bash
+dotnet new hexagonal-solution-contracts -h
+```
+
If you want to update the template to the latest version just execute the following command
```bash
diff --git a/templates/Bff/.template.config/template.json b/templates/Bff/.template.config/template.json
index 04e60023..daaf4758 100644
--- a/templates/Bff/.template.config/template.json
+++ b/templates/Bff/.template.config/template.json
@@ -7,9 +7,11 @@
"shortName": "hexagonal-solution-bff",
"sourceName": "Hexagonal.Solution.Template.Bff",
"classifications": [
- "common",
- "template",
- "hexagonal-architecture"
+ "Common",
+ "Template",
+ "Hexagonal-Architecture",
+ "Backend-For-Frontend",
+ "BFF"
],
"tags": {
"language": "C#",
diff --git a/templates/Contracts/.editorconfig b/templates/Contracts/.editorconfig
new file mode 100644
index 00000000..c4f02cf9
--- /dev/null
+++ b/templates/Contracts/.editorconfig
@@ -0,0 +1,197 @@
+# editorconfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 4 spaces as indentation
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+# Generated code
+[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}]
+generated_code = true
+
+# C# files
+[*.cs]
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+
+# avoid this. unless absolutely necessary
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+# Types: use keywords instead of BCL types, and permit var only when the type is clear
+csharp_style_var_for_built_in_types = false:suggestion
+csharp_style_var_when_type_is_apparent = false:none
+csharp_style_var_elsewhere = true:suggestion
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# name all constant fields using PascalCase
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# static fields should have s_ prefix
+dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
+dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+dotnet_naming_symbols.static_fields.applicable_kinds = field
+dotnet_naming_symbols.static_fields.required_modifiers = static
+dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
+dotnet_naming_style.static_prefix_style.required_prefix = s_
+dotnet_naming_style.static_prefix_style.capitalization = camel_case
+
+# internal and private fields should be _camelCase
+dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
+dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
+dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
+dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
+dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
+dotnet_naming_style.camel_case_underscore_style.required_prefix = _
+dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
+
+# Code style defaults
+csharp_using_directive_placement = outside_namespace:suggestion
+dotnet_sort_system_directives_first = true
+csharp_prefer_braces = true:silent
+csharp_preserve_single_line_blocks = true:none
+csharp_preserve_single_line_statements = false:none
+csharp_prefer_static_local_function = true:suggestion
+csharp_prefer_simple_using_statement = false:none
+csharp_style_prefer_switch_expression = true:suggestion
+csharp_style_namespace_declarations = file_scoped:warning
+dotnet_style_readonly_field = true:suggestion
+
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+csharp_prefer_simple_default_expression = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_constructors = true:silent
+csharp_style_expression_bodied_operators = true:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = true:silent
+
+# Pattern matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+
+# Null checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Other features
+csharp_style_prefer_index_operator = false:none
+csharp_style_prefer_range_operator = false:none
+csharp_style_pattern_local_over_anonymous_function = false:none
+
+# Space preferences
+csharp_space_after_cast = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# S3216: "ConfigureAwait(false)" should be used
+dotnet_diagnostic.S3216.severity = none
+
+# S2360: Optional parameters should not be used
+dotnet_diagnostic.S2360.severity = none
+
+# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+dotnet_diagnostic.CS8618.severity = none
+
+# CA1873: Avoid potentially expensive logging
+dotnet_diagnostic.CA1873.severity = none
+
+# Xml project files
+[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
+indent_size = 2
+
+[*.{csproj,vbproj,proj,nativeproj,locproj}]
+charset = utf-8
+
+# Xml build files
+[*.builds]
+indent_size = 2
+
+# Xml files
+[*.{xml,stylecop,resx,ruleset}]
+indent_size = 2
+
+# Xml config files
+[*.{props,targets,config,nuspec}]
+indent_size = 2
+
+# YAML config files
+[*.{yml,yaml}]
+indent_size = 2
+
+# Shell scripts
+[*.sh]
+end_of_line = lf
+[*.{cmd,bat}]
+end_of_line = crlf
+
+[*.{sql,json}]
+insert_final_newline = false
diff --git a/templates/Contracts/.template.config/template.json b/templates/Contracts/.template.config/template.json
new file mode 100644
index 00000000..addb30ec
--- /dev/null
+++ b/templates/Contracts/.template.config/template.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Giovanni Brunno Previatti",
+ "identity": "Hexagonal.Solution.Template.Contracts",
+ "name": "Hexagonal solution template Contracts",
+ "description": "Solution template focused on shared contracts for hexagonal architecture",
+ "shortName": "hexagonal-solution-contracts",
+ "sourceName": "Hexagonal.Solution.Template.Contracts",
+ "classifications": [
+ "Common",
+ "Template",
+ "Hexagonal-Architecture",
+ "Contracts"
+ ],
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "preferNameDirectory": true
+}
\ No newline at end of file
diff --git a/templates/Contracts/Directory.Build.props b/templates/Contracts/Directory.Build.props
new file mode 100644
index 00000000..5053742d
--- /dev/null
+++ b/templates/Contracts/Directory.Build.props
@@ -0,0 +1,12 @@
+
+
+ net10.0
+ enable
+ enable
+ true
+ latest
+ Recommended
+ true
+ false
+
+
\ No newline at end of file
diff --git a/templates/Contracts/Directory.Packages.props b/templates/Contracts/Directory.Packages.props
new file mode 100644
index 00000000..ce856b10
--- /dev/null
+++ b/templates/Contracts/Directory.Packages.props
@@ -0,0 +1,14 @@
+
+
+ true
+ true
+ $(NoWarn);NU1507
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/Contracts/Hexagonal.Solution.Template.Contracts.slnx b/templates/Contracts/Hexagonal.Solution.Template.Contracts.slnx
new file mode 100644
index 00000000..0d1901e5
--- /dev/null
+++ b/templates/Contracts/Hexagonal.Solution.Template.Contracts.slnx
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/Contracts/Readme.md b/templates/Contracts/Readme.md
new file mode 100644
index 00000000..fb9333ff
--- /dev/null
+++ b/templates/Contracts/Readme.md
@@ -0,0 +1,31 @@
+# Hexagonal Architecture Contracts Template
+
+This repository provides a focused contracts template for applications using Hexagonal Architecture. The template includes shared request/response contracts, DTOs, and gRPC contract definitions with unit tests to validate contract instantiation.
+
+## Project Structure
+
+- `src/`: Contains shared contracts.
+ - `Contracts/`: Common base contracts, order contracts, and proto files.
+
+- `tests/`: Contains tests for contract correctness.
+ - `UnitTests/`: Instantiation tests for all contract records and DTOs.
+
+## Helper Commands
+
+### Restore dependencies
+
+```bash
+dotnet restore
+```
+
+### Build contracts project
+
+```bash
+dotnet build src/Contracts
+```
+
+### Run unit tests
+
+```bash
+dotnet test tests/UnitTests
+```
diff --git a/templates/Contracts/src/Contracts/Common/BaseRequest.cs b/templates/Contracts/src/Contracts/Common/BaseRequest.cs
new file mode 100644
index 00000000..80440f48
--- /dev/null
+++ b/templates/Contracts/src/Contracts/Common/BaseRequest.cs
@@ -0,0 +1,25 @@
+namespace Contracts.Common;
+
+///
+/// Base request structure
+///
+/// The unique identifier for correlating requests
+public record BaseRequest(Guid CorrelationId);
+
+///
+/// Base paginated request structure
+///
+/// The unique identifier for correlating requests
+/// The page number to retrieve
+/// The number of items per page
+/// The field to sort by
+/// Indicates whether the sorting is in descending order
+/// A dictionary of search criteria
+public record BasePaginatedRequest(
+ Guid CorrelationId,
+ int Page = 1,
+ int PageSize = 10,
+ string? SortBy = null,
+ bool SortDescending = false,
+ Dictionary? SearchByValues = null
+) : BaseRequest(CorrelationId);
\ No newline at end of file
diff --git a/templates/Contracts/src/Contracts/Common/BaseResponse.cs b/templates/Contracts/src/Contracts/Common/BaseResponse.cs
new file mode 100644
index 00000000..440f33c6
--- /dev/null
+++ b/templates/Contracts/src/Contracts/Common/BaseResponse.cs
@@ -0,0 +1,99 @@
+namespace Contracts.Common;
+
+///
+/// Base response structure
+///
+public record BaseResponse
+{
+ ///
+ /// Base response structure
+ ///
+ public BaseResponse() { }
+
+ ///
+ /// Base response structure
+ ///
+ /// Indicates whether the response is successful
+ /// Optional message providing additional information
+ public BaseResponse(bool success, string? message = null)
+ {
+ Success = success;
+ Message = message;
+ }
+
+ ///
+ /// Indicates whether the response is successful
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// Optional message providing additional information
+ ///
+ public string? Message { get; set; }
+}
+
+///
+/// Generic base response structure with data
+///
+/// The type of the data included in the response
+public record BaseResponse : BaseResponse where TData : class
+{
+ ///
+ /// Generic base response structure with data
+ ///
+ public BaseResponse() { }
+
+ ///
+ /// Generic base response structure with data
+ ///
+ /// Indicates whether the response is successful
+ /// The data included in the response
+ /// Optional message providing additional information
+ public BaseResponse(bool success, TData? data = null, string? message = null) : base(success, message) => Data = data;
+
+ ///
+ /// The data included in the response
+ ///
+ public TData? Data { get; set; }
+
+}
+
+///
+/// Generic base paginated response structure with data
+///
+///
+public sealed record BasePaginatedResponse : BaseResponse>
+{
+ ///
+ /// Generic base paginated response structure with data
+ ///
+ public BasePaginatedResponse() { }
+
+ ///
+ /// Generic base paginated response structure with data
+ ///
+ /// Indicates whether the response is successful
+ /// The total number of pages available
+ /// The total number of records available
+ /// The data included in the response
+ /// Optional message providing additional information
+ public BasePaginatedResponse(
+ bool success, int totalPages, int totalRecords,
+ IEnumerable? data = null, string? message = null
+ ) : base(success, data, message)
+ {
+ TotalPages = totalPages;
+ TotalRecords = totalRecords;
+ }
+
+ ///
+ /// The total number of pages available
+ ///
+ public int TotalPages { get; set; }
+
+ ///
+ /// The total number of records available
+ ///
+ public int TotalRecords { get; set; }
+
+}
diff --git a/templates/Contracts/src/Contracts/Contracts.csproj b/templates/Contracts/src/Contracts/Contracts.csproj
new file mode 100644
index 00000000..be735abb
--- /dev/null
+++ b/templates/Contracts/src/Contracts/Contracts.csproj
@@ -0,0 +1,11 @@
+
+
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/Contracts/src/Contracts/Orders/CreateOrderRequest.cs b/templates/Contracts/src/Contracts/Orders/CreateOrderRequest.cs
new file mode 100644
index 00000000..3a67a0da
--- /dev/null
+++ b/templates/Contracts/src/Contracts/Orders/CreateOrderRequest.cs
@@ -0,0 +1,23 @@
+using Contracts.Common;
+
+namespace Contracts.Orders;
+
+///
+/// Request to create a new order
+///
+/// The unique identifier for the request
+/// A description of the order
+/// The items included in the order
+public sealed record CreateOrderRequest(
+ Guid CorrelationId,
+ string Description,
+ CreateOrderItemRequest[] Items
+) : BaseRequest(CorrelationId);
+
+///
+/// An item included in the order
+///
+/// The name of the item
+/// A description of the item
+/// The value of the item
+public sealed record CreateOrderItemRequest(string Name, string Description, decimal Value);
\ No newline at end of file
diff --git a/templates/Contracts/src/Contracts/Orders/OrderDto.cs b/templates/Contracts/src/Contracts/Orders/OrderDto.cs
new file mode 100644
index 00000000..591c252e
--- /dev/null
+++ b/templates/Contracts/src/Contracts/Orders/OrderDto.cs
@@ -0,0 +1,55 @@
+namespace Contracts.Orders;
+
+///
+/// Data transfer object representing an order
+///
+public sealed record OrderDto
+{
+ ///
+ /// The unique identifier of the order
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// A description of the order
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// The total value of the order
+ ///
+ /// 99.99
+ public decimal Total { get; set; }
+
+ ///
+ /// The items included in the order
+ ///
+ public IReadOnlyCollection? Items { get; set; }
+};
+
+///
+/// Data transfer object representing an item in an order
+///
+public sealed record ItemDto
+{
+ ///
+ /// The unique identifier of the item
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// The name of the item
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// A description of the item
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// The value of the item
+ ///
+ /// 99.99
+ public decimal Value { get; set; }
+};
diff --git a/templates/Contracts/src/Contracts/Protos/payment.proto b/templates/Contracts/src/Contracts/Protos/payment.proto
new file mode 100644
index 00000000..17d64541
--- /dev/null
+++ b/templates/Contracts/src/Contracts/Protos/payment.proto
@@ -0,0 +1,33 @@
+syntax = "proto3";
+
+option csharp_namespace = "GrpcPayment";
+
+package Protos;
+
+// Service for managing payments
+service PaymentService {
+ // Creates a payment request for an order
+ rpc Create (CreatePaymentRequest) returns (PaymentReply);
+}
+
+// Request message for creating a payment
+message CreatePaymentRequest {
+ // Unique identifier for the order
+ int32 order_id = 1;
+ // Amount to be paid
+ double amount = 2;
+ // Correlation ID for tracking requests
+ string correlation_id = 3;
+ // Currency of the payment
+ string currency = 4;
+ // Method of payment
+ string payment_method = 5;
+}
+
+// Reply message for payment processing
+message PaymentReply {
+ // Indicates if the payment was successful
+ bool success = 1;
+ // Message providing additional information
+ string message = 2;
+}
\ No newline at end of file
diff --git a/templates/Contracts/src/Contracts/packages.lock.json b/templates/Contracts/src/Contracts/packages.lock.json
new file mode 100644
index 00000000..31f901c7
--- /dev/null
+++ b/templates/Contracts/src/Contracts/packages.lock.json
@@ -0,0 +1,190 @@
+{
+ "version": 2,
+ "dependencies": {
+ "net10.0": {
+ "Grpc.AspNetCore": {
+ "type": "Direct",
+ "requested": "[2.76.0, )",
+ "resolved": "2.76.0",
+ "contentHash": "LyXMmpN2Ba0TE35SOLSKbGqIYtJuhc1UgiaGfoW1X8KJERV70QI5KGW+ckEY7MrXoFWN/uWo4B70siVhbDmCgQ==",
+ "dependencies": {
+ "Google.Protobuf": "3.31.1",
+ "Grpc.AspNetCore.Server.ClientFactory": "2.76.0",
+ "Grpc.Tools": "2.76.0"
+ }
+ },
+ "Google.Protobuf": {
+ "type": "Transitive",
+ "resolved": "3.31.1",
+ "contentHash": "gSnJbUmGiOTdWddPhqzrEscHq9Ls6sqRDPB9WptckyjTUyx70JOOAaDLkFff8gManZNN3hllQ4aQInnQyq/Z/A=="
+ },
+ "Grpc.AspNetCore.Server": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "diSC/ZeNdSdxHdYSOpYwuSBBDYpuNVtJQFJfiBB0WrYOQ4lVMmdxuUZJcViahQyo8pCvS3Mueo5lqFxwwMF/iw==",
+ "dependencies": {
+ "Grpc.Net.Common": "2.76.0"
+ }
+ },
+ "Grpc.AspNetCore.Server.ClientFactory": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "y5KGO1GO0N2L/hCCMR05mmoK8j+v8rKvZ+9nothAxKx2Tf2CwV8f4TM5K0GkKfDsp4vrc4lm90MU6E+DeN7YIw==",
+ "dependencies": {
+ "Grpc.AspNetCore.Server": "2.76.0",
+ "Grpc.Net.ClientFactory": "2.76.0"
+ }
+ },
+ "Grpc.Core.Api": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "cSxC2tdnFdXXuBgIn1pjc4YBx7LXTCp4M0qn+SMBS35VWZY+cEQYLWTBDDhdBH1HzU7BV+ncVZlniGQHMpRJKQ=="
+ },
+ "Grpc.Net.Client": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "K1oldmqw2+Gn69nGRzZLhqSiUZwelX1GrBu/cUl9wNf1C0uB61vFS6JcxUUv9P8VoUJhFsmV44JA6lI2EUt4xw==",
+ "dependencies": {
+ "Grpc.Net.Common": "2.76.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
+ }
+ },
+ "Grpc.Net.ClientFactory": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "XI+kO69L9AV8B9N0UQOmH911r6MOEp9huHiavEsY56DJYuzJ9KAxNGy37dpV6CLbgCaN2uKmpOsZ9Pao6bmpVQ==",
+ "dependencies": {
+ "Grpc.Net.Client": "2.76.0",
+ "Microsoft.Extensions.Http": "8.0.0"
+ }
+ },
+ "Grpc.Net.Common": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "bZpiMVYgvpB44/wBh1RotrkqC7bg2FOasLri2GhR3hMKyzsiTxCoDE49YjPrJeFc4RW0wS8u+EInI09sjxVFRA==",
+ "dependencies": {
+ "Grpc.Core.Api": "2.76.0"
+ }
+ },
+ "Grpc.Tools": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "goRzYZVMgQtyLvkWdgTnPycg49hlNxnMkqGGgR3l7nCOm0bUh0YeAneiJ9JFk3XLgF4suQUdETYkl2Mg/TBr0w=="
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Http": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Diagnostics": "8.0.0",
+ "Microsoft.Extensions.Logging": "8.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "8.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.Binder": "8.0.0",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/templates/Contracts/tests/UnitTests/Common/BaseRequestTests.cs b/templates/Contracts/tests/UnitTests/Common/BaseRequestTests.cs
new file mode 100644
index 00000000..4e722abe
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/Common/BaseRequestTests.cs
@@ -0,0 +1,57 @@
+using Contracts.Common;
+
+namespace UnitTests.Common;
+
+public sealed class BaseRequestTests
+{
+ [Fact(DisplayName = nameof(GivenABaseRequestWhenInstantiatedThenShouldAssignCorrelationId))]
+ public void GivenABaseRequestWhenInstantiatedThenShouldAssignCorrelationId()
+ {
+ var correlationId = Guid.NewGuid();
+
+ var request = new BaseRequest(correlationId);
+
+ Assert.Equal(correlationId, request.CorrelationId);
+ }
+
+ [Fact(DisplayName = nameof(GivenABasePaginatedRequestWhenUsingDefaultValuesThenShouldAssignExpectedDefaults))]
+ public void GivenABasePaginatedRequestWhenUsingDefaultValuesThenShouldAssignExpectedDefaults()
+ {
+ var correlationId = Guid.NewGuid();
+
+ var request = new BasePaginatedRequest(correlationId);
+
+ Assert.Equal(correlationId, request.CorrelationId);
+ Assert.Equal(1, request.Page);
+ Assert.Equal(10, request.PageSize);
+ Assert.Null(request.SortBy);
+ Assert.False(request.SortDescending);
+ Assert.Null(request.SearchByValues);
+ }
+
+ [Fact(DisplayName = nameof(GivenABasePaginatedRequestWhenInstantiatedThenShouldAssignCustomValues))]
+ public void GivenABasePaginatedRequestWhenInstantiatedThenShouldAssignCustomValues()
+ {
+ var correlationId = Guid.NewGuid();
+ var searchByValues = new Dictionary
+ {
+ ["status"] = "active"
+ };
+
+ var request = new BasePaginatedRequest(
+ correlationId,
+ Page: 2,
+ PageSize: 25,
+ SortBy: "CreatedAt",
+ SortDescending: true,
+ SearchByValues: searchByValues
+ );
+
+ Assert.Equal(correlationId, request.CorrelationId);
+ Assert.Equal(2, request.Page);
+ Assert.Equal(25, request.PageSize);
+ Assert.Equal("CreatedAt", request.SortBy);
+ Assert.True(request.SortDescending);
+ Assert.Equal(searchByValues, request.SearchByValues);
+ }
+}
diff --git a/templates/Contracts/tests/UnitTests/Common/BaseResponseTests.cs b/templates/Contracts/tests/UnitTests/Common/BaseResponseTests.cs
new file mode 100644
index 00000000..eb5c7381
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/Common/BaseResponseTests.cs
@@ -0,0 +1,71 @@
+using Contracts.Common;
+using Contracts.Orders;
+
+namespace UnitTests.Common;
+
+public sealed class BaseResponseTests
+{
+ [Fact(DisplayName = nameof(GivenABaseResponseWhenInstantiatedWithDefaultsThenShouldKeepDefaultValues))]
+ public void GivenABaseResponseWhenInstantiatedWithDefaultsThenShouldKeepDefaultValues()
+ {
+ var response = new BaseResponse();
+
+ Assert.False(response.Success);
+ Assert.Null(response.Message);
+ }
+
+ [Fact(DisplayName = nameof(GivenABaseResponseWhenInstantiatedThenShouldAssignSuccessAndMessage))]
+ public void GivenABaseResponseWhenInstantiatedThenShouldAssignSuccessAndMessage()
+ {
+ var response = new BaseResponse(true, "ok");
+
+ Assert.True(response.Success);
+ Assert.Equal("ok", response.Message);
+ }
+
+ [Fact(DisplayName = nameof(GivenABaseResponseOfTDataWhenInstantiatedThenShouldAssignSuccessMessageAndData))]
+ public void GivenABaseResponseOfTDataWhenInstantiatedThenShouldAssignSuccessMessageAndData()
+ {
+ var data = new OrderDto
+ {
+ Id = 1,
+ Description = "Order 1",
+ Total = 99.9m
+ };
+
+ var response = new BaseResponse(true, data, "created");
+
+ Assert.True(response.Success);
+ Assert.Equal("created", response.Message);
+ Assert.Equal(data, response.Data);
+ }
+
+ [Fact(DisplayName = nameof(GivenABasePaginatedResponseWhenInstantiatedThenShouldAssignPaginationAndData))]
+ public void GivenABasePaginatedResponseWhenInstantiatedThenShouldAssignPaginationAndData()
+ {
+ ItemDto[] data =
+ [
+ new()
+ {
+ Id = 1,
+ Name = "Item 1",
+ Description = "Desc 1",
+ Value = 10m
+ }
+ ];
+
+ var response = new BasePaginatedResponse(
+ success: true,
+ totalPages: 4,
+ totalRecords: 30,
+ data,
+ message: "ok"
+ );
+
+ Assert.True(response.Success);
+ Assert.Equal("ok", response.Message);
+ Assert.Equal(4, response.TotalPages);
+ Assert.Equal(30, response.TotalRecords);
+ Assert.Equal(data, response.Data);
+ }
+}
diff --git a/templates/Contracts/tests/UnitTests/GlobalUsings.cs b/templates/Contracts/tests/UnitTests/GlobalUsings.cs
new file mode 100644
index 00000000..c802f448
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
diff --git a/templates/Contracts/tests/UnitTests/Orders/CreateOrderRequestTests.cs b/templates/Contracts/tests/UnitTests/Orders/CreateOrderRequestTests.cs
new file mode 100644
index 00000000..277dab9e
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/Orders/CreateOrderRequestTests.cs
@@ -0,0 +1,33 @@
+using Contracts.Orders;
+
+namespace UnitTests.Orders;
+
+public sealed class CreateOrderRequestTests
+{
+ [Fact(DisplayName = nameof(GivenACreateOrderItemRequestWhenInstantiatedThenShouldAssignProperties))]
+ public void GivenACreateOrderItemRequestWhenInstantiatedThenShouldAssignProperties()
+ {
+ var request = new CreateOrderItemRequest("Item 1", "Description 1", 120.5m);
+
+ Assert.Equal("Item 1", request.Name);
+ Assert.Equal("Description 1", request.Description);
+ Assert.Equal(120.5m, request.Value);
+ }
+
+ [Fact(DisplayName = nameof(GivenACreateOrderRequestWhenInstantiatedThenShouldAssignProperties))]
+ public void GivenACreateOrderRequestWhenInstantiatedThenShouldAssignProperties()
+ {
+ var correlationId = Guid.NewGuid();
+ CreateOrderItemRequest[] items =
+ [
+ new("Item 1", "Description 1", 100m),
+ new("Item 2", "Description 2", 200m)
+ ];
+
+ var request = new CreateOrderRequest(correlationId, "Order description", items);
+
+ Assert.Equal(correlationId, request.CorrelationId);
+ Assert.Equal("Order description", request.Description);
+ Assert.Equal(items, request.Items);
+ }
+}
diff --git a/templates/Contracts/tests/UnitTests/Orders/OrderDtoTests.cs b/templates/Contracts/tests/UnitTests/Orders/OrderDtoTests.cs
new file mode 100644
index 00000000..69399446
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/Orders/OrderDtoTests.cs
@@ -0,0 +1,51 @@
+using Contracts.Orders;
+
+namespace UnitTests.Orders;
+
+public sealed class OrderDtoTests
+{
+ [Fact(DisplayName = nameof(GivenAnOrderDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues))]
+ public void GivenAnOrderDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues()
+ {
+ ItemDto[] items =
+ [
+ new()
+ {
+ Id = 5,
+ Name = "Item A",
+ Description = "Item A description",
+ Value = 45.7m
+ }
+ ];
+
+ var orderDto = new OrderDto
+ {
+ Id = 10,
+ Description = "Order A",
+ Total = 45.7m,
+ Items = items
+ };
+
+ Assert.Equal(10, orderDto.Id);
+ Assert.Equal("Order A", orderDto.Description);
+ Assert.Equal(45.7m, orderDto.Total);
+ Assert.Equal(items, orderDto.Items);
+ }
+
+ [Fact(DisplayName = nameof(GivenAnItemDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues))]
+ public void GivenAnItemDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues()
+ {
+ var itemDto = new ItemDto
+ {
+ Id = 2,
+ Name = "Item B",
+ Description = "Item B description",
+ Value = 99.99m
+ };
+
+ Assert.Equal(2, itemDto.Id);
+ Assert.Equal("Item B", itemDto.Name);
+ Assert.Equal("Item B description", itemDto.Description);
+ Assert.Equal(99.99m, itemDto.Value);
+ }
+}
diff --git a/templates/Contracts/tests/UnitTests/UnitTests.csproj b/templates/Contracts/tests/UnitTests/UnitTests.csproj
new file mode 100644
index 00000000..cd8a842e
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/UnitTests.csproj
@@ -0,0 +1,25 @@
+
+
+ false
+ true
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/Contracts/tests/UnitTests/packages.lock.json b/templates/Contracts/tests/UnitTests/packages.lock.json
new file mode 100644
index 00000000..5bede809
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/packages.lock.json
@@ -0,0 +1,293 @@
+{
+ "version": 2,
+ "dependencies": {
+ "net10.0": {
+ "coverlet.collector": {
+ "type": "Direct",
+ "requested": "[8.0.1, )",
+ "resolved": "8.0.1",
+ "contentHash": "heVQl5tKYnnIDYlR1QMVGueYH6iriZTcZB6AjDczQNwZzxkjDIt9C84Pt4cCiZYrbo7jkZOYGWbs6Lo9wAtVLg=="
+ },
+ "Microsoft.NET.Test.Sdk": {
+ "type": "Direct",
+ "requested": "[18.3.0, )",
+ "resolved": "18.3.0",
+ "contentHash": "xW3kXuWRQtgoxJp4J+gdhHSQyK+6Wb/AZDSd7lMvuMRYlZ1tnpkojyfZlWilB5G4dmZ0Y0ZxU/M23TlubndNkw==",
+ "dependencies": {
+ "Microsoft.CodeCoverage": "18.3.0",
+ "Microsoft.TestPlatform.TestHost": "18.3.0"
+ }
+ },
+ "xunit": {
+ "type": "Direct",
+ "requested": "[2.9.3, )",
+ "resolved": "2.9.3",
+ "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
+ "dependencies": {
+ "xunit.analyzers": "1.18.0",
+ "xunit.assert": "2.9.3",
+ "xunit.core": "[2.9.3]"
+ }
+ },
+ "xunit.runner.visualstudio": {
+ "type": "Direct",
+ "requested": "[3.1.5, )",
+ "resolved": "3.1.5",
+ "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA=="
+ },
+ "Google.Protobuf": {
+ "type": "Transitive",
+ "resolved": "3.31.1",
+ "contentHash": "gSnJbUmGiOTdWddPhqzrEscHq9Ls6sqRDPB9WptckyjTUyx70JOOAaDLkFff8gManZNN3hllQ4aQInnQyq/Z/A=="
+ },
+ "Grpc.AspNetCore.Server": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "diSC/ZeNdSdxHdYSOpYwuSBBDYpuNVtJQFJfiBB0WrYOQ4lVMmdxuUZJcViahQyo8pCvS3Mueo5lqFxwwMF/iw==",
+ "dependencies": {
+ "Grpc.Net.Common": "2.76.0"
+ }
+ },
+ "Grpc.AspNetCore.Server.ClientFactory": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "y5KGO1GO0N2L/hCCMR05mmoK8j+v8rKvZ+9nothAxKx2Tf2CwV8f4TM5K0GkKfDsp4vrc4lm90MU6E+DeN7YIw==",
+ "dependencies": {
+ "Grpc.AspNetCore.Server": "2.76.0",
+ "Grpc.Net.ClientFactory": "2.76.0"
+ }
+ },
+ "Grpc.Core.Api": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "cSxC2tdnFdXXuBgIn1pjc4YBx7LXTCp4M0qn+SMBS35VWZY+cEQYLWTBDDhdBH1HzU7BV+ncVZlniGQHMpRJKQ=="
+ },
+ "Grpc.Net.Client": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "K1oldmqw2+Gn69nGRzZLhqSiUZwelX1GrBu/cUl9wNf1C0uB61vFS6JcxUUv9P8VoUJhFsmV44JA6lI2EUt4xw==",
+ "dependencies": {
+ "Grpc.Net.Common": "2.76.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
+ }
+ },
+ "Grpc.Net.ClientFactory": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "XI+kO69L9AV8B9N0UQOmH911r6MOEp9huHiavEsY56DJYuzJ9KAxNGy37dpV6CLbgCaN2uKmpOsZ9Pao6bmpVQ==",
+ "dependencies": {
+ "Grpc.Net.Client": "2.76.0",
+ "Microsoft.Extensions.Http": "8.0.0"
+ }
+ },
+ "Grpc.Net.Common": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "bZpiMVYgvpB44/wBh1RotrkqC7bg2FOasLri2GhR3hMKyzsiTxCoDE49YjPrJeFc4RW0wS8u+EInI09sjxVFRA==",
+ "dependencies": {
+ "Grpc.Core.Api": "2.76.0"
+ }
+ },
+ "Grpc.Tools": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "goRzYZVMgQtyLvkWdgTnPycg49hlNxnMkqGGgR3l7nCOm0bUh0YeAneiJ9JFk3XLgF4suQUdETYkl2Mg/TBr0w=="
+ },
+ "Microsoft.CodeCoverage": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "23BNy/vziREC20Wwhb50K7+kZe0m07KlLWDQv4qjJ9tt3QjpDpDIqJFrhYHmMEo9xDkuSp55U/8h4bMF7MiB+g=="
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Http": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Diagnostics": "8.0.0",
+ "Microsoft.Extensions.Logging": "8.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "8.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.Binder": "8.0.0",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
+ },
+ "Microsoft.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "AEIEX2aWdPO9XbtR96eBaJxmXRD9vaI9uQ1T/JbPEKlTAZwYx0ZrMzKyULMdh/HH9Sg03kXCoN7LszQ90o6nPQ=="
+ },
+ "Microsoft.TestPlatform.TestHost": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "twmsoelXnp1uWMU3VGip9f0Jr1mZ0PZqgJdF35CIrdYgYrkHIJMV1m8uKyhcdjLdsQDESHAgkR7KhS9i1qpJag==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "18.3.0",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.3",
+ "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
+ },
+ "xunit.abstractions": {
+ "type": "Transitive",
+ "resolved": "2.0.3",
+ "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
+ },
+ "xunit.analyzers": {
+ "type": "Transitive",
+ "resolved": "1.18.0",
+ "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
+ },
+ "xunit.assert": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
+ },
+ "xunit.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]",
+ "xunit.extensibility.execution": "[2.9.3]"
+ }
+ },
+ "xunit.extensibility.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
+ "dependencies": {
+ "xunit.abstractions": "2.0.3"
+ }
+ },
+ "xunit.extensibility.execution": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]"
+ }
+ },
+ "contracts": {
+ "type": "Project",
+ "dependencies": {
+ "Grpc.AspNetCore": "[2.76.0, )"
+ }
+ },
+ "Grpc.AspNetCore": {
+ "type": "CentralTransitive",
+ "requested": "[2.76.0, )",
+ "resolved": "2.76.0",
+ "contentHash": "LyXMmpN2Ba0TE35SOLSKbGqIYtJuhc1UgiaGfoW1X8KJERV70QI5KGW+ckEY7MrXoFWN/uWo4B70siVhbDmCgQ==",
+ "dependencies": {
+ "Google.Protobuf": "3.31.1",
+ "Grpc.AspNetCore.Server.ClientFactory": "2.76.0",
+ "Grpc.Tools": "2.76.0"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/templates/Contracts/tests/UnitTests/stryker-config.json b/templates/Contracts/tests/UnitTests/stryker-config.json
new file mode 100644
index 00000000..d462dee3
--- /dev/null
+++ b/templates/Contracts/tests/UnitTests/stryker-config.json
@@ -0,0 +1,15 @@
+{
+ "stryker-config": {
+ "reporters": [
+ "progress",
+ "html"
+ ],
+ "project": "Contracts.csproj",
+ "report-file-name": "mutation-tests-contracts-result",
+ "thresholds": {
+ "high": 90,
+ "low": 80,
+ "break": 50
+ }
+ }
+}
\ No newline at end of file
diff --git a/templates/Full/.template.config/template.json b/templates/Full/.template.config/template.json
index b9791c55..0ebd8e69 100644
--- a/templates/Full/.template.config/template.json
+++ b/templates/Full/.template.config/template.json
@@ -7,9 +7,10 @@
"shortName": "hexagonal-solution-full",
"sourceName": "Hexagonal.Solution.Template.Full",
"classifications": [
- "common",
- "template",
- "hexagonal-architecture"
+ "Common",
+ "Template",
+ "Hexagonal-Architecture",
+ "Full"
],
"tags": {
"language": "C#",
diff --git a/templates/Full/.vscode/launch.json b/templates/Full/.vscode/launch.json
deleted file mode 100644
index 1243368b..00000000
--- a/templates/Full/.vscode/launch.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "WebApp [Development]",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "build",
- "program": "${workspaceFolder}/src/WebApp/bin/Debug/net10.0/WebApp.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/WebApp",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/templates/Full/.vscode/tasks.json b/templates/Full/.vscode/tasks.json
deleted file mode 100644
index 6407faab..00000000
--- a/templates/Full/.vscode/tasks.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "version": "2.0.0",
- "tasks": [
- {
- "label": "build",
- "command": "dotnet",
- "type": "process",
- "args": [
- "build",
- "${workspaceFolder}/src/WebApp/WebApp.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary;ForceNoAlign"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "watch",
- "command": "dotnet",
- "type": "process",
- "args": [
- "watch",
- "run",
- "--project",
- "${workspaceFolder}/src/WebApp/WebApp.csproj"
- ],
- "problemMatcher": "$msCompile"
- }
- ]
-}
\ No newline at end of file