diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..b70e488
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,62 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "altcover.global": {
+ "version": "8.8.74",
+ "commands": [
+ "altcover"
+ ],
+ "rollForward": false
+ },
+ "cake.tool": {
+ "version": "2.3.0",
+ "commands": [
+ "dotnet-cake"
+ ],
+ "rollForward": false
+ },
+ "dotnet-format": {
+ "version": "5.1.250801",
+ "commands": [
+ "dotnet-format"
+ ],
+ "rollForward": false
+ },
+ "csharpier": {
+ "version": "0.28.2",
+ "commands": [
+ "dotnet-csharpier"
+ ],
+ "rollForward": false
+ },
+ "swashbuckle.aspnetcore.cli": {
+ "version": "6.6.2",
+ "commands": [
+ "swagger"
+ ],
+ "rollForward": false
+ },
+ "microsoft.tye": {
+ "version": "0.11.0-alpha.22111.1",
+ "commands": [
+ "tye"
+ ],
+ "rollForward": false
+ },
+ "dotnet-ef": {
+ "version": "8.0.7",
+ "commands": [
+ "dotnet-ef"
+ ],
+ "rollForward": false
+ },
+ "dotnet-outdated-tool": {
+ "version": "4.6.4",
+ "commands": [
+ "dotnet-outdated"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.csharpierrc.yaml b/.csharpierrc.yaml
new file mode 100644
index 0000000..51dd694
--- /dev/null
+++ b/.csharpierrc.yaml
@@ -0,0 +1,8 @@
+# https://csharpier.com/docs/Configuration
+printWidth: 120
+useTabs: false
+tabWidth: 4
+preprocessorSymbolSets:
+ - ""
+ - "DEBUG"
+ - "DEBUG,CODE_STYLE"
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..5b49199
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,7 @@
+# https://containers.dev/guide/dockerfile#dockerfile
+# https://github.com/devcontainers/images/tree/main/src/dotnet
+# https://github.com/devcontainers/templates/tree/main/src/dotnet
+
+FROM mcr.microsoft.com/devcontainers/dotnet:latest
+
+# Add additional commands
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..fdbe284
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,130 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
+// https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/setting-up-your-dotnet-project-for-codespaces
+// https://github.com/microsoft/vscode-remote-try-dotnet
+// https://dev.to/this-is-learning/set-up-github-codespaces-for-a-net-8-application-5999
+// https://audacioustux.notion.site/Getting-Started-with-Devcontainer-c727dbf9d56f4d6b9b0ef87b3111693f
+{
+ "name": "Movie Search Application",
+ // use existing dev container templates. More info: https://containers.dev/guide/dockerfile, https://containers.dev/templates
+ //"image": "mcr.microsoft.com/devcontainers/dotnet:1-7.0",
+ // use a Dockerfile file. More info: https://containers.dev/guide/dockerfile#dockerfile
+ // "build": {
+ // // Path is relative to the devcontainer.json file.
+ // "dockerfile": "Dockerfile"
+ // },
+ // using a Dockerfile with Docker Compose, https://containers.dev/guide/dockerfile#docker-compose-image
+ "dockerComposeFile": "docker-compose.yaml",
+ "service": "devcontainer",
+ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ "features": {
+ // https://github.com/devcontainers/features/tree/main/src/dotnet#dotnet-cli-dotnet
+ "ghcr.io/devcontainers/features/dotnet:2": {
+ // this version should be matched with global.json .net version for working vscode IntelliSense correctly
+ "version": "8.0.303",
+ "additionalVersions": "latest, 8.0.303",
+ "aspNetCoreRuntimeVersions": "latest"
+ },
+ // https://github.com/devcontainers/features/tree/main/src/github-cli
+ "ghcr.io/devcontainers/features/github-cli:1": {
+ "version": "2"
+ },
+ // https://github.com/devcontainers/features/tree/main/src/powershell
+ "ghcr.io/devcontainers/features/powershell:1": {
+ "version": "latest"
+ },
+ // // https://github.com/devcontainers/features/tree/main/src/kubectl-helm-minikube
+ // "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {},
+ // // https://github.com/devcontainers/features/tree/main/src/terraform
+ // "ghcr.io/devcontainers/features/terraform:1": {},
+ // https://github.com/devcontainers/features/tree/main/src/docker-in-docker
+ // https://devopscube.com/run-docker-in-docker/
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {
+ "dockerDashComposeVersion": "v2"
+ },
+ // https://github.com/devcontainers/features/tree/main/src/node
+ "ghcr.io/devcontainers/features/node:1": {},
+ "ghcr.io/devcontainers/features/git:1": {},
+ // https://github.com/devcontainers/features/tree/main/src/common-utils
+ "ghcr.io/devcontainers/features/common-utils:2": {
+ "configureZshAsDefaultShell": true
+ }
+ },
+ // Configure tool-specific properties.
+ "customizations": {
+ // Configure properties specific to VS Code.
+ "vscode": {
+ "settings": {
+ "git.autofetch": true,
+ "files.autoSave": "onFocusChange",
+ "editor.formatOnSave": true,
+ "editor.suggest.snippetsPreventQuickSuggestions": false,
+ "explorer.autoReveal": true,
+ "resmon.show.cpufreq": false,
+ "dotnet.defaultSolution": "movie-search.sln",
+ "dotnet.server.startTimeout": 60000,
+ "omnisharp.projectLoadTimeout": 60,
+ "workbench.colorTheme": "Visual Studio Light",
+ "workbench.iconTheme": "material-icon-theme",
+ "editor.minimap.enabled": false,
+ "editor.fontFamily": "'MesloLGM Nerd Font', 'Droid Sans Mono', 'monospace', 'Droid Sans Fallback', 'Consolas'",
+ "editor.fontSize": 14,
+ "explorer.confirmDelete": false,
+ "terminal.integrated.defaultProfile.windows": "PowerShell",
+ "terminal.integrated.defaultProfile.linux": "zsh",
+ "powershell.cwd": "~",
+ "terminal.external.windowsExec": "%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[jsonc]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "editor.smoothScrolling": true,
+ "editor.wordWrap": "on",
+ "editor.guides.bracketPairs": true,
+ "explorer.experimental.fileNesting.enabled": true,
+ "[csharp]": {
+ "editor.defaultFormatter": "csharpier.csharpier-vscode",
+ "editor.formatOnSave": true
+ }
+ },
+ "extensions": [
+ "streetsidesoftware.code-spell-checker",
+ "ms-dotnettools.csdevkit",
+ "mutantdino.resourcemonitor",
+ "humao.rest-client",
+ "dzhavat.bracket-pair-toggler",
+ "ms-azuretools.vscode-docker",
+ "vivaxy.vscode-conventional-commits",
+ "emmanuelbeziat.vscode-great-icons",
+ "ms-vscode.vs-keybindings",
+ "GitHub.vscode-github-actions",
+ "PKief.material-icon-theme",
+ "EditorConfig.EditorConfig",
+ "DavidAnson.vscode-markdownlint",
+ "IBM.output-colorizer", // Colorize your output/test logs
+ "emmanuelbeziat.vscode-great-icons",
+ "esbenp.prettier-vscode",
+ "vscode-icons-team.vscode-icons",
+ "redhat.vscode-yaml"
+ ]
+ }
+ },
+ "hostRequirements": {
+ "cpus": 4,
+ "memory": "8gb",
+ "storage": "32gb"
+ },
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [5000, 5001],
+ // "portsAttributes": {
+ // "5001": {
+ // "protocol": "https"
+ // }
+ // }
+ // https://containers.dev/implementors/json_reference/#lifecycle-scripts
+ "updateContentCommand": "chmod +x .devcontainer/scripts/update.sh",
+ "postCreateCommand": "chmod +x .devcontainer/scripts/post-create.sh"
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml
new file mode 100644
index 0000000..8d8adc2
--- /dev/null
+++ b/.devcontainer/docker-compose.yaml
@@ -0,0 +1,26 @@
+# https://containers.dev/guide/dockerfile#docker-compose-image
+# https://containers.dev/guide/dockerfile#docker-compose-dockerfile
+services:
+ devcontainer:
+ build:
+ context: .
+ volumes:
+ - ../..:/workspaces:rw,cached
+ init: true
+ command: sleep infinity
+
+# services:
+# devcontainer:
+# image: mcr.microsoft.com/devcontainers/dotnet:3.1.0
+# volumes:
+# - ../..:/workspaces:cached
+# network_mode: service:db
+# command: sleep infinity
+
+# db:
+# image: postgres:latest
+# restart: unless-stopped
+# environment:
+# POSTGRES_PASSWORD: postgres
+# POSTGRES_USER: postgres
+# POSTGRES_DB: postgres
diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh
new file mode 100755
index 0000000..e84ede0
--- /dev/null
+++ b/.devcontainer/scripts/post-create.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -eax
+
+# Run the setup_fonts.sh script
+./setup-fonts.sh
diff --git a/.devcontainer/scripts/setup-fonts.sh b/.devcontainer/scripts/setup-fonts.sh
new file mode 100644
index 0000000..cda8f12
--- /dev/null
+++ b/.devcontainer/scripts/setup-fonts.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# Download MesloLGM Nerd Font
+wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/Meslo.zip -O MesloLGM.zip
+
+# Extract the font files
+unzip MesloLGM.zip -d MesloLGM
+
+# Create the fonts directory if it doesn't exist
+mkdir -p ~/.local/share/fonts
+
+# Move the font files to the fonts directory
+mv MesloLGM/*.ttf ~/.local/share/fonts/
+
+# Update the font cache
+fc-cache -fv
+
+# Clean up
+rm -rf MesloLGM.zip MesloLGM
+
+# Verify installation
+fc-list | grep "MesloLGM"
+
+echo "Font setup completed."
diff --git a/.devcontainer/scripts/update.sh b/.devcontainer/scripts/update.sh
new file mode 100755
index 0000000..99a8bb9
--- /dev/null
+++ b/.devcontainer/scripts/update.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+set -eax
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..7c322a9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,28 @@
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.idea
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/secrets.dev.yaml
+**/values.dev.yaml
+**/bin/
+**/obj/
+**/out/
+LICENSE
+README.md
+local/data/
+portainer-data/
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..d238d3a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,555 @@
+# https://editorconfig.org
+# https://www.jetbrains.com/help/resharper/Using_EditorConfig.html
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files
+# When opening a file, EditorConfig plugins look for a file named .editorconfig in the directory of the opened file and in every parent directory. A search for .editorconfig files will stop if the root filepath is reached or an EditorConfig file with `root=true` is found.
+
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+##################################################################################
+## https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/
+## https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
+## Microsoft Rules
+##
+
+# C# files
+[*.{cs, cshtml}]
+indent_size = 4
+
+# 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_within_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+
+# 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
+
+# only use var when it's obvious what the variable type is
+# csharp_style_var_for_built_in_types = false:none
+# csharp_style_var_when_type_is_apparent = false:none
+# csharp_style_var_elsewhere = false:suggestion
+
+# use language keywords instead of BCL types
+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
+
+dotnet_naming_symbols.static_fields.applicable_kinds = field
+dotnet_naming_symbols.static_fields.required_modifiers = static
+
+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
+dotnet_sort_system_directives_first = true
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+
+# 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
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = false:none
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_operators = false:none
+csharp_style_expression_bodied_properties = true:none
+csharp_style_expression_bodied_indexers = true:none
+csharp_style_expression_bodied_accessors = true:none
+
+# 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
+
+# Space preferences
+csharp_space_after_cast = false
+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_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
+
+# CA1305: Specify IFormatProvider
+dotnet_diagnostic.ca1305.severity = None
+
+# CA1063: Implement IDisposable correctly
+dotnet_diagnostic.ca1063.severity = None
+
+# CA2201: Do not raise reserved exception types
+dotnet_diagnostic.ca2201.severity = Suggestion
+
+# CA1848: Use the LoggerMessage delegates
+dotnet_diagnostic.ca1848.severity = Suggestion
+
+# CA1810: Initialize reference type static fields inline
+dotnet_diagnostic.ca1810.severity = Suggestion
+
+# CA1725: Parameter names should match base declaration
+dotnet_diagnostic.ca1725.severity = Suggestion
+
+# https://csharpier.com/docs/IntegratingWithLinters#code-analysis-rules
+dotnet_diagnostic.ide0055.severity = none
+
+##################################################################################
+# https://jetbrains.com.xy2401.com/help/resharper/EditorConfig_Index.html
+# https://jetbrains.com.xy2401.com/help/resharper/Reference__Code_Inspections_CSHARP.html
+## Resharper
+
+# ReSharper properties
+resharper_align_linq_query = true
+resharper_align_multiline_array_and_object_initializer = true
+resharper_align_multiline_binary_patterns = true
+resharper_align_multiline_expression = true
+resharper_align_multiline_extends_list = true
+resharper_align_multiline_parameter = true
+resharper_align_multiline_property_pattern = true
+resharper_align_multiline_switch_expression = true
+resharper_align_multiple_declaration = true
+resharper_align_multline_type_parameter_constrains = true
+resharper_align_multline_type_parameter_list = true
+resharper_align_tuple_components = true
+resharper_braces_for_for = required_for_multiline
+resharper_braces_for_foreach = required_for_multiline
+resharper_braces_for_ifelse = required_for_multiline
+resharper_csharp_alignment_tab_fill_style = optimal_fill
+resharper_csharp_indent_type_constraints = false
+resharper_csharp_int_align_fix_in_adjacent = false
+resharper_csharp_outdent_commas = true
+resharper_csharp_stick_comment = false
+resharper_csharp_wrap_after_declaration_lpar = true
+resharper_csharp_wrap_after_invocation_lpar = true
+resharper_csharp_wrap_arguments_style = chop_if_long
+resharper_csharp_wrap_before_declaration_rpar = true
+resharper_csharp_wrap_before_first_type_parameter_constraint = true
+resharper_csharp_wrap_before_ternary_opsigns = false
+resharper_csharp_wrap_extends_list_style = chop_if_long
+resharper_csharp_wrap_multiple_declaration_style = wrap_if_long
+resharper_csharp_wrap_multiple_type_parameter_constraints_style = chop_always
+resharper_csharp_wrap_parameters_style = chop_if_long
+resharper_enforce_line_ending_style = true
+resharper_indent_anonymous_method_block = true
+resharper_indent_braces_inside_statement_conditions = false
+resharper_indent_nested_fixed_stmt = true
+resharper_indent_nested_foreach_stmt = true
+resharper_indent_nested_for_stmt = true
+resharper_indent_nested_lock_stmt = true
+resharper_indent_nested_usings_stmt = true
+resharper_indent_nested_while_stmt = true
+resharper_keep_existing_declaration_block_arrangement = true
+resharper_keep_existing_declaration_parens_arrangement = false
+resharper_keep_existing_embedded_arrangement = false
+resharper_keep_existing_embedded_block_arrangement = true
+resharper_keep_existing_enum_arrangement = true
+resharper_keep_existing_initializer_arrangement = false
+resharper_keep_existing_invocation_parens_arrangement = false
+resharper_keep_existing_property_patterns_arrangement = false
+resharper_keep_existing_switch_expression_arrangement = false
+resharper_max_array_initializer_elements_on_line = 700
+resharper_max_formal_parameters_on_line = 500
+resharper_max_invocation_arguments_on_line = 700
+resharper_new_line_before_while = true
+resharper_place_attribute_on_same_line = false
+resharper_place_linq_into_on_new_line = false
+resharper_place_simple_case_statement_on_same_line = if_owner_is_single_line
+resharper_place_simple_property_pattern_on_single_line = false
+resharper_show_autodetect_configure_formatting_tip = false
+resharper_space_within_single_line_array_initializer_braces = false
+resharper_trailing_comma_in_multiline_lists = true
+resharper_trailing_comma_in_singleline_lists = true
+resharper_use_heuristics_for_body_style = false
+resharper_use_indent_from_vs = false
+resharper_use_roslyn_logic_for_evident_types = true
+resharper_wrap_array_initializer_style = chop_always
+resharper_wrap_chained_binary_expressions = chop_if_long
+resharper_wrap_chained_binary_patterns = chop_if_long
+resharper_wrap_chained_method_calls = chop_if_long
+resharper_wrap_for_stmt_header_style = wrap_if_long
+resharper_wrap_switch_expression = chop_if_long
+resharper_wrap_verbatim_interpolated_strings = chop_if_long
+
+# ReSharper inspection severities
+resharper_arrange_accessor_owner_body_highlighting = none
+resharper_arrange_redundant_parentheses_highlighting = hint
+resharper_arrange_type_member_modifiers_highlighting = hint
+resharper_arrange_type_modifiers_highlighting = hint
+resharper_check_namespace_highlighting = none
+resharper_enforce_if_statement_braces_highlighting = hint
+resharper_inconsistent_naming_highlighting = suggestion
+resharper_static_member_in_generic_type_highlighting = none
+resharper_suggest_var_or_type_built_in_types_highlighting = hint
+resharper_suggest_var_or_type_elsewhere_highlighting = hint
+resharper_suggest_var_or_type_simple_types_highlighting = hint
+resharper_web_config_module_not_resolved_highlighting = warning
+resharper_web_config_type_not_resolved_highlighting = warning
+resharper_web_config_wrong_module_highlighting = warning
+
+
+##################################################################################
+## https://github.com/DotNetAnalyzers/StyleCopAnalyzers/tree/master/documentation
+## https://documentation.help/StyleCop/StyleCop.html
+## StyleCop.Analyzers
+##
+
+# Using directive should appear within a namespace declaration
+dotnet_diagnostic.sa1200.severity = None
+
+# Generic type parameter documentation should have text.
+dotnet_diagnostic.sa1622.severity = None
+
+# XML comment analysis is disabled due to project configuration
+dotnet_diagnostic.sa0001.severity = None
+
+# The file header is missing or not located at the top of the file
+dotnet_diagnostic.sa1633.severity = None
+
+# Use string.Empty for empty strings
+dotnet_diagnostic.sa1122.severity = None
+
+# Variable '_' should begin with lower-case letter
+dotnet_diagnostic.sa1312.severity = None
+
+# Parameter '_' should begin with lower-case letter
+dotnet_diagnostic.sa1313.severity = None
+
+# Elements should be documented
+dotnet_diagnostic.sa1600.severity = None
+
+# Prefix local calls with this
+dotnet_diagnostic.sa1101.severity = None
+
+# 'public' members should come before 'private' members
+dotnet_diagnostic.sa1202.severity = None
+
+# Comments should contain text
+dotnet_diagnostic.sa1120.severity = None
+
+# Constant fields should appear before non-constant fields
+dotnet_diagnostic.sa1203.severity = None
+
+# Field '_blah' should not begin with an underscore
+dotnet_diagnostic.sa1309.severity = None
+
+# Use trailing comma in multi-line initializers
+dotnet_diagnostic.sa1413.severity = None
+
+# A method should not follow a class
+dotnet_diagnostic.sa1201.severity = None
+
+# Elements should be separated by blank line
+dotnet_diagnostic.sa1516.severity = None
+
+# The parameter spans multiple lines
+dotnet_diagnostic.sa1118.severity = None
+
+# Static members should appear before non-static members
+dotnet_diagnostic.sa1204.severity = None
+
+# Put constructor initializers on their own line
+dotnet_diagnostic.sa1128.severity = None
+
+# Opening braces should not be preceded by blank line
+dotnet_diagnostic.sa1509.severity = None
+
+# The parameter should begin on the line after the previous parameter
+dotnet_diagnostic.sa1115.severity = None
+
+# File name should match first type name
+dotnet_diagnostic.sa1649.severity = None
+
+# File may only contain a single type
+dotnet_diagnostic.sa1402.severity = None
+
+# Enumeration items should be documented
+dotnet_diagnostic.sa1602.severity = None
+
+# Element should not be on a single line
+dotnet_diagnostic.sa1502.severity = None
+
+# Closing parenthesis should not be preceded by a space
+dotnet_diagnostic.sa1009.severity = None
+
+# Closing parenthesis should be on line of last parameter
+dotnet_diagnostic.sa1111.severity = None
+
+# Braces should not be ommitted
+dotnet_diagnostic.sa1503.severity = None
+
+dotnet_diagnostic.sa1401.severity = None
+
+# The parameters to a C# method or indexer call or declaration are not all on the same line or each on a separate line.
+# dotnet_diagnostic.SA1117.severity = Suggestion
+
+# The parameters to a C# method or indexer call or declaration span across multiple lines, but the first parameter does not start on the line after the opening bracket.
+# dotnet_diagnostic.SA1116.severity = Suggestion
+
+# A C# partial element is missing a documentation header.
+dotnet_diagnostic.sa1601.severity = None
+
+# A tag within a C# element’s documentation header is empty.
+dotnet_diagnostic.sa1614.severity = None
+
+# A C# element is missing documentation for its return value.
+dotnet_diagnostic.sa1615.severity = None
+
+# The tag within a C# element’s documentation header is empty.
+dotnet_diagnostic.sa1616.severity = None
+
+# An opening brace within a C# element is not spaced correctly.
+dotnet_diagnostic.sa1012.severity = Suggestion
+
+# A closing brace within a C# element is not spaced correctly.
+dotnet_diagnostic.sa1013.severity = Suggestion
+
+# A call to an instance member of the local class or a base class is not prefixed with 'this.', within a C# code file.
+dotnet_diagnostic.sa1101.severity = None
+
+# The keywords within the declaration of an element do not follow a standard ordering scheme.
+dotnet_diagnostic.SA1206.severity = None
+
+##################################################################################
+## https://github.com/meziantou/Meziantou.Analyzer/tree/main/docs
+## Meziantou.Analyzer
+
+# MA0004: Use Task.ConfigureAwait(false)
+dotnet_diagnostic.ma0004.severity = Suggestion
+
+# MA0049: Type name should not match containing namespace
+dotnet_diagnostic.ma0049.severity = Suggestion
+
+# MA0048: File name must match type name
+dotnet_diagnostic.ma0048.severity = Suggestion
+
+# MA0051: Method is too long
+dotnet_diagnostic.ma0051.severity = Suggestion
+
+# https://www.meziantou.net/string-comparisons-are-harder-than-it-seems.htm
+# MA0006 - Use String.Equals instead of equality operator
+dotnet_diagnostic.ma0006.severity = Suggestion
+
+# MA0002 - IEqualityComparer or IComparer is missing
+dotnet_diagnostic.ma0002.severity = Suggestion
+
+# MA0001 - StringComparison is missing
+dotnet_diagnostic.ma0001.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#13-pass-cancellation-token
+# MA0040: Specify a cancellation token
+dotnet_diagnostic.ma0032.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#13-pass-cancellation-token
+# MA0040: Flow the cancellation token when available
+dotnet_diagnostic.ma0040.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#14-using-cancellation-token-with-iasyncenumerable
+# MA0079: Use a cancellation token using .WithCancellation()
+dotnet_diagnostic.ma0079.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#14-using-cancellation-token-with-iasyncenumerable
+# MA0080: Use a cancellation token using .WithCancellation()
+dotnet_diagnostic.ma0080.severity = Suggestion
+
+# MA0047 - Declare types in namespaces
+# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0047.md
+dotnet_diagnostic.ma0047.severity = Suggestion
+
+# MA0015 - Specify the parameter name in ArgumentException
+dotnet_diagnostic.ma0015.severity = Suggestion
+
+##################################################################################
+## http://pihrt.net/Roslynator/Analyzers
+## http://pihrt.net/Roslynator/Refactorings
+## https://github.com/JosefPihrt/Roslynator/blob/main/docs/Configuration.md
+## Roslynator
+
+# RCS1036 - Remove redundant empty line.
+dotnet_diagnostic.rcs1036.severity = None
+
+# RCS1037 - Remove trailing white-space.
+dotnet_diagnostic.rcs1037.severity = None
+
+# RCS1194: Implement exception constructors
+dotnet_diagnostic.rcs1194.severity = None
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#1-redundant-asyncawait
+# RCS1174: Remove redundant async/await.
+dotnet_diagnostic.rcs1174.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#10-returning-null-from-a-task-returning-method
+# RCS1210: Return Task.FromResult instead of returning null.
+dotnet_diagnostic.rcs1210.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#9-missing-configureawaitbool
+# RCS1090: Call 'ConfigureAwait(false)'.
+dotnet_diagnostic.rcs1090.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#11-asynchronous-method-names-should-end-with-async
+#RCS1046: Asynchronous method name should end with 'Async'.
+dotnet_diagnostic.rcs1046.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#12-non-asynchronous-method-names-shouldnt-end-with-async
+# RCS1047: Non-asynchronous method name should not end with 'Async'.
+dotnet_diagnostic.rcs1047.severity = error
+
+# https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1174.md
+# RCS1174: Remove redundant async/await
+dotnet_diagnostic.rcs1174.severity = Suggestion
+
+
+
+##################################################################################
+## https://github.com/semihokur/asyncfixer
+## AsyncFixer01
+##
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#1-redundant-asyncawait
+# AsyncFixer01: Unnecessary async/await usage
+dotnet_diagnostic.asyncfixer01.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#2-calling-synchronous-method-inside-the-async-method
+# AsyncFixer02: Long-running or blocking operations inside an async method
+dotnet_diagnostic.asyncfixer02.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#3-async-void-method
+# AsyncFixer03: Fire & forget async void methods
+dotnet_diagnostic.asyncfixer03.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#6-not-awaited-task-inside-the-using-block
+# AsyncFixer04: Fire & forget async call inside a using block
+dotnet_diagnostic.asyncfixer04.severity = error
+
+
+##################################################################################
+## https://github.com/microsoft/vs-threading
+## Microsoft.VisualStudio.Threading.Analyzers
+##
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#2-calling-synchronous-method-inside-the-async-method
+# VSTHRD103: Call async methods when in an async method
+dotnet_diagnostic.vsthrd103.severity = Suggestion
+
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#3-async-void-method
+# VSTHRD100: Avoid async void methods
+dotnet_diagnostic.vsthrd100.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#4-unsupported-async-delegates
+# VSTHRD101: Avoid unsupported async delegates
+dotnet_diagnostic.vsthrd101.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#5-not-awaited-task-within-using-expression
+# VSTHRD107: Await Task within using expression
+dotnet_diagnostic.vsthrd107.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p1/#7-unobserved-result-of-asynchronous-method
+# VSTHRD110: Observe result of async calls
+dotnet_diagnostic.vsthrd110.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits
+# VSTHRD002: Avoid problematic synchronous waits
+dotnet_diagnostic.vsthrd002.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#9-missing-configureawaitbool
+# VSTHRD111: Use ConfigureAwait(bool)
+dotnet_diagnostic.vsthrd111.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#10-returning-null-from-a-task-returning-method
+# VSTHRD114: Avoid returning a null Task
+dotnet_diagnostic.vsthrd114.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#11-asynchronous-method-names-should-end-with-async
+# VSTHRD200: Use "Async" suffix for async methods
+dotnet_diagnostic.vsthrd200.severity = Suggestion
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#12-non-asynchronous-method-names-shouldnt-end-with-async
+# VSTHRD200: Use "Async" suffix for async methods
+dotnet_diagnostic.vsthrd200.severity = Suggestion
+
+
+##################################################################################
+## https://github.com/hvanbakel/Asyncify-CSharp
+## Asyncify
+##
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits
+# AsyncifyInvocation: Use Task Async
+dotnet_diagnostic.asyncifyinvocation.severity = error
+
+# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits
+# AsyncifyVariable: Use Task Async
+dotnet_diagnostic.asyncifyvariable.severity = error
+
+
+##################################################################################
+## https://github.com/cezarypiatek/ExceptionAnalyzer
+
+dotnet_diagnostic.ex001.severity = Suggestion
+
+[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
+indent_style = space
+indent_size = 4
+tab_width = 4
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.github/workflows/build-dot-net.yml b/.github/workflows/build-dot-net.yml
index 10b16eb..2381e1d 100644
--- a/.github/workflows/build-dot-net.yml
+++ b/.github/workflows/build-dot-net.yml
@@ -2,22 +2,22 @@ name: build-dot-net
on:
push:
- branches: [ main ]
+ branches: [main]
pull_request:
- branches: [ main ]
+ branches: [main]
jobs:
build:
-
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Setup .NET
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: 7.0.x
- - name: Restore dependencies
- run: dotnet restore
- - name: Build
- run: dotnet build --no-restore
+ - uses: actions/checkout@v3
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: "8.0.x"
+
+ - name: Restore dependencies
+ run: dotnet restore
+ - name: Build
+ run: dotnet build --no-restore
diff --git a/.gitignore b/.gitignore
index 12f5774..f699e6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,7 +23,6 @@ mono_crash.*
[Rr]eleases/
x64/
x86/
-[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
@@ -62,9 +61,6 @@ project.lock.json
project.fragment.lock.json
artifacts/
-# ASP.NET Scaffolding
-ScaffoldingReadMe.txt
-
# StyleCop
StyleCopReport.xml
@@ -90,7 +86,6 @@ StyleCopReport.xml
*.tmp_proj
*_wpftmp.csproj
*.log
-*.tlog
*.vspscc
*.vssscc
.builds
@@ -142,11 +137,6 @@ _TeamCity*
.axoCover/*
!.axoCover/settings.json
-# Coverlet is a free, cross platform Code Coverage Tool
-coverage*.json
-coverage*.xml
-coverage*.info
-
# Visual Studio code coverage results
*.coverage
*.coveragexml
@@ -196,7 +186,7 @@ PublishScripts/
*.nupkg
# NuGet Symbol Packages
*.snupkg
-# The packages folder can be ignored because of Package Restore
+# The packages folder can be ignored because of Package Reecommerce
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
@@ -206,9 +196,6 @@ PublishScripts/
*.nuget.props
*.nuget.targets
-# Nuget personal access tokens and Credentials
-# nuget.config
-
# Microsoft Azure Build Output
csx/
*.build.csdef
@@ -217,10 +204,8 @@ csx/
ecf/
rcf/
-# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
-Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
@@ -362,112 +347,32 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
-# Fody - auto-generated XML schema
-FodyWeavers.xsd
-
-# VS Code files for those working on multiple tools
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-*.code-workspace
-
-# Local History for Visual Studio Code
-.history/
-
-# Windows Installer files from build outputs
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
+.tye/
+.logs/
+.Log
-# JetBrains Rider
+out/
+**/.vagrant/
.idea/
-*.sln.iml
-
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-*.code-workspace
+local/data/
+portainer-data/
+docker.sock/
+localtime/
+coveragereport/
+CoverageResults/
+coverage.cobertura.xml
+coverage.info
# Local History for Visual Studio Code
.history/
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
+# Built Visual Studio Code Extensions
+*.vsix
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
+# exclude sensitive env files
+.env
+.env.dev
+.denv.prod
+.env.catalogs
+.env.customers
+.env.identity
\ No newline at end of file
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..c160a77
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx --no -- commitlint --edit ${1}
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..001bdf6
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+dotnet csharpier . && git add -A .
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..a7330d5
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,33 @@
+{
+ "git.autofetch": true,
+ "files.autoSave": "onFocusChange",
+ "editor.formatOnSave": true,
+ "editor.suggest.snippetsPreventQuickSuggestions": false,
+ "explorer.autoReveal": true,
+ "resmon.show.cpufreq": false,
+ "dotnet.defaultSolution": "movie-search.sln",
+ "dotnet.server.startTimeout": 60000,
+ "omnisharp.projectLoadTimeout": 60,
+ "workbench.colorTheme": "Visual Studio Light",
+ "workbench.iconTheme": "material-icon-theme",
+ "editor.minimap.enabled": false,
+ "editor.fontFamily": "'MesloLGM Nerd Font', 'Droid Sans Mono', 'monospace', 'Droid Sans Fallback', 'Consolas'",
+ "editor.fontSize": 14,
+ "explorer.confirmDelete": false,
+ "terminal.integrated.defaultProfile.windows": "PowerShell",
+ "terminal.integrated.defaultProfile.linux": "zsh",
+ "powershell.cwd": "~",
+ "terminal.external.windowsExec": "%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "explorer.experimental.fileNesting.enabled": true,
+ "[jsonc]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "editor.smoothScrolling": true,
+ "editor.wordWrap": "on",
+ "editor.guides.bracketPairs": true,
+ "[csharp]": {
+ "editor.defaultFormatter": "ms-dotnettools.csharp",
+ "editor.formatOnSave": true
+ }
+}
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000..69b4242
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ["@commitlint/config-conventional"],
+};
diff --git a/deployments/docker-compose/docker-compose.yaml b/deployments/docker-compose/docker-compose.yaml
index 836ed8d..55bab7a 100644
--- a/deployments/docker-compose/docker-compose.yaml
+++ b/deployments/docker-compose/docker-compose.yaml
@@ -1,4 +1,4 @@
-version: '3'
+version: '3.9'
services:
movie.api:
container_name: movie.api
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..9e5db15
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "8.0.303",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/movie-search.sln b/movie-search.sln
index a778108..1a3fb60 100644
--- a/movie-search.sln
+++ b/movie-search.sln
@@ -29,27 +29,42 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovieSearch.UnitTests", "te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovieSearch.EndToEndTests", "tests\MovieSearch.EndToEndTests\MovieSearch.EndToEndTests.csproj", "{13E831B9-FDFB-4873-B0A8-B131CFEB357F}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{B2B37F1B-728F-4C54-9BB9-2B75A138D9F6}"
+ ProjectSection(SolutionItems) = preProject
+ .csharpierrc.yaml = .csharpierrc.yaml
+ .dockerignore = .dockerignore
+ .editorconfig = .editorconfig
+ .gitattributes = .gitattributes
+ .gitignore = .gitignore
+ commitlint.config.js = commitlint.config.js
+ global.json = global.json
+ movie-search.sln = movie-search.sln
+ nuget.config = nuget.config
+ package.json = package.json
+ readme.md = readme.md
+ stylecop.json = stylecop.json
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deployments", "deployments", "{FA998ABA-B0B3-40F9-9941-0C3CBA15EADB}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker-compose", "docker-compose", "{ADF1E782-F7A1-46AF-8620-55CC8A82AEBD}"
+ ProjectSection(SolutionItems) = preProject
+ deployments\docker-compose\docker-compose.yaml = deployments\docker-compose\docker-compose.yaml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k8s", "k8s", "{C3F744FD-F978-40A4-A884-F79B0C6B0F59}"
+ ProjectSection(SolutionItems) = preProject
+ deployments\k8s\delete-resources.sh = deployments\k8s\delete-resources.sh
+ deployments\k8s\deploy-local.sh = deployments\k8s\deploy-local.sh
+ deployments\k8s\movie-search-api.yaml = deployments\k8s\movie-search-api.yaml
+ deployments\k8s\movie-search-ingress.yaml = deployments\k8s\movie-search-ingress.yaml
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {55EED072-1C18-46DA-B491-6971715C3912} = {778BA20A-056F-46DC-AB48-95259206D8BE}
- {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} = {778BA20A-056F-46DC-AB48-95259206D8BE}
- {B40C04AA-D156-4289-8D7F-9463925BC222} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
- {C0B80C21-9FA9-4C27-A9BB-92497A98307A} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
- {F4F6EE60-D819-46E9-8CA2-69ECAEE04863} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
- {4E3C0119-A28C-4FAC-9A72-170BE69B8F95} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
- {7BC58153-8348-480E-B8BE-4ABFECE38309} = {55EED072-1C18-46DA-B491-6971715C3912}
- {10EA36EB-5832-4C67-86BC-54CEF4AE5DD0} = {55EED072-1C18-46DA-B491-6971715C3912}
- {B19EA418-80E2-42F8-8FB8-AB5277BB9E70} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6}
- {A389E4BB-9E51-4124-8A22-691B12B66C73} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6}
- {13E831B9-FDFB-4873-B0A8-B131CFEB357F} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6}
- EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B40C04AA-D156-4289-8D7F-9463925BC222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B40C04AA-D156-4289-8D7F-9463925BC222}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -88,4 +103,23 @@ Global
{13E831B9-FDFB-4873-B0A8-B131CFEB357F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13E831B9-FDFB-4873-B0A8-B131CFEB357F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {B40C04AA-D156-4289-8D7F-9463925BC222} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
+ {C0B80C21-9FA9-4C27-A9BB-92497A98307A} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
+ {F4F6EE60-D819-46E9-8CA2-69ECAEE04863} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
+ {4E3C0119-A28C-4FAC-9A72-170BE69B8F95} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E}
+ {55EED072-1C18-46DA-B491-6971715C3912} = {778BA20A-056F-46DC-AB48-95259206D8BE}
+ {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} = {778BA20A-056F-46DC-AB48-95259206D8BE}
+ {7BC58153-8348-480E-B8BE-4ABFECE38309} = {55EED072-1C18-46DA-B491-6971715C3912}
+ {10EA36EB-5832-4C67-86BC-54CEF4AE5DD0} = {55EED072-1C18-46DA-B491-6971715C3912}
+ {B19EA418-80E2-42F8-8FB8-AB5277BB9E70} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6}
+ {A389E4BB-9E51-4124-8A22-691B12B66C73} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6}
+ {13E831B9-FDFB-4873-B0A8-B131CFEB357F} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6}
+ {FA998ABA-B0B3-40F9-9941-0C3CBA15EADB} = {B2B37F1B-728F-4C54-9BB9-2B75A138D9F6}
+ {ADF1E782-F7A1-46AF-8620-55CC8A82AEBD} = {FA998ABA-B0B3-40F9-9941-0C3CBA15EADB}
+ {C3F744FD-F978-40A4-A884-F79B0C6B0F59} = {FA998ABA-B0B3-40F9-9941-0C3CBA15EADB}
+ EndGlobalSection
EndGlobal
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..6ce9759
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..da573f6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "movie-search-application",
+ "version": "1.0.0",
+ "description": "movie-search-application",
+ "scripts": {
+ "prepare": "husky install && dotnet tool restore",
+ "install-dev-cert-bash": "curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v vs2019 -l ~/vsdbg"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/mehdihadeli/movie-search-application.git"
+ },
+ "author": "",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/mehdihadeli/movie-search-application/issues"
+ },
+ "homepage": "https://github.com/mehdihadeli/movie-search-application#readme",
+ "devDependencies": {
+ "@commitlint/cli": "^19.2.1",
+ "@commitlint/config-conventional": "^19.1.0",
+ "husky": "^9.0.11",
+ "npm-check-updates": "^16.14.18"
+ }
+}
diff --git a/readme.md b/readme.md
index 16444e8..66d345c 100644
--- a/readme.md
+++ b/readme.md
@@ -2,13 +2,15 @@
[![Actions Status](https://github.com/mehdihadeli/movie-search-app/workflows/build-dot-net/badge.svg?branch=main)](https://github.com/mehdihadeli/movie-search-app/actions)
+[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/mehdihadeli/movie-search-application)
+
Movies Search Application is a search engine for searching movies and tv-shows with using two popular apis [TMDB](https://developers.themoviedb.org/3) and [Youtube API](https://developers.google.com/youtube/v3) for get all information about the movies and its related trailers with some other features.
This app is based on .Net core and vertical slices architecture and I wrote some level of tests like [Unit Testing](./tests/MovieSearch.UnitTests), [Integration Testing](./tests/MovieSearch.IntegrationTests) and [End-To-End Testing](./tests/MovieSearch.EndToEndTests) for this application.
## Application Structure
-In this application I used a [mediator pattern](https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/) with using [MediatR](https://github.com/jbogard/MediatR) library in my controllers for a clean and [thin controller](https://codeopinion.com/thin-controllers-cqrs-mediatr/), also instead of using a `application service` class because after some times our controller will depends to different services and this breaks single responsibility principle. We use mediator pattern to manage the delivery of messages to handlers. One of the advantages behind the [mediator pattern](https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/) is that it allows the application code to define a pipeline of activities for requests . For example in our controllers we create a command and send it to mediator and mediator will route our command to a specific command handler in application layer.
+In this application I used a [mediator pattern](https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/) with using [MediatR](https://github.com/jbogard/MediatR) library in my controllers for a clean and [thin controller](https://codeopinion.com/thin-controllers-cqrs-mediatr/), also instead of using a `application service` class because after some times our controller will depends to different services and this breaks single responsibility principle. We use mediator pattern to manage the delivery of messages to handlers. One of the advantages behind the [mediator pattern](https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/) is that it allows the application code to define a pipeline of activities for requests . For example in our controllers we create a command and send it to mediator and mediator will route our command to a specific command handler in application layer.
To support [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle) and [Don't Repeat Yourself principles](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), the implementation of cross-cutting concerns is done using the mediatr [pipeline behaviors](https://github.com/jbogard/MediatR/wiki/Behaviors) or creating a [mediatr decorators](https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/).
@@ -26,23 +28,24 @@ Also here I used cqrs for decompose my features to very small parts that make ou
- maximize performance, scalability and simplicity.
- adding new feature to this mechanism is very easy without any breaking change in other part of our codes. New features only add code, we're not changing shared code and worrying about side effects.
- easy to maintain and any changes only affect on one command or query (or a slice) and avoid any breaking changes on other parts
-- it gives us better separation of concerns and cross cutting concern (with help of mediatr behavior pipelines) in our code instead of a big service class for doing a lot of things.
+- it gives us better separation of concerns and cross cutting concern (with help of mediatr behavior pipelines) in our code instead of a big service class for doing a lot of things.
+With using CQRS pattern, we cut each business functionality into some vertical slices, and inner each of this slices we have [technical folders structure](http://www.kamilgrzybek.com/design/feature-folders/) specific to that feature (command, handlers, infrastructure, repository, controllers, ...). In Our CQRS pattern each command/query handler is a separate slice. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, we can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when we change the core generic mechanism in one layer, it can impact all methods.
-With using CQRS pattern, we cut each business functionality into some vertical slices, and inner each of this slices we have [technical folders structure](http://www.kamilgrzybek.com/design/feature-folders/) specific to that feature (command, handlers, infrastructure, repository, controllers, ...). In Our CQRS pattern each command/query handler is a separate slice. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, we can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when we change the core generic mechanism in one layer, it can impact all methods.
+For checking `validation rules` we use two type of validation:
-For checking `validation rules` we use two type of validation:
- [Data Validation](http://www.kamilgrzybek.com/design/rest-api-data-validation/): Data validation verify data items which are coming to our application from external sources and check if theirs values are acceptable but Business rules validation is a more broad concept and more close to how business works and behaves. So it is mainly focused on behavior For implementing data validation I used [FluentValidation](https://github.com/FluentValidation/FluentValidation) library for cleaner validation also better separation of concern in my handlers for preventing mixing validation logic with orchestration logic in my handlers.
- [Business Rules validation](http://www.kamilgrzybek.com/design/domain-model-validation/): I explicitly check all of the our business rules, inner my handlers and I will throw a customized exception based on the error that these errors should inherits from [AppException](./src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs) class, because of these exceptions, occurs in application layer and we catch this exceptions in api layer with using [Hellang.Middleware.ProblemDetails](https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails/) middleware and pass a correct status code to client.
-
+
Examples of `data validation` :
1- Input Validation
+
- We want to ensure our Id is greater than zero.
In bellow validator for our query as request we check that Id is greater than zero
-``` csharp
+```csharp
public class FindMovieByIdQueryValidator : AbstractValidator
{
public FindMovieByIdQueryValidator()
@@ -53,9 +56,10 @@ public class FindMovieByIdQueryValidator : AbstractValidator
```
2- Business Rule Validation
+
- We want to check our database contains a movie with specific Id and if there is no movie with this Id, we throw a [MovieNotFoundException](./src/MovieSearch.Application/Movies/Exceptions/MovieNotFoundException.cs).
-``` csharp
+```csharp
var movie = await _movieDbServiceClient.GetMovieByIdAsync(query.Id, cancellationToken);
if (movie is null)
@@ -64,7 +68,7 @@ if (movie is null)
Also for handling exceptions and correct status codes in our web api response, I Used [Hellang.Middleware.ProblemDetails](https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails/) package and I config and map all our needed exceptions and their corresponding status code in our Infrastructure layer and [AddInfrastructure](./src/MovieSearch.Infrastructure/Extensions.cs) method.
-``` csharp
+```csharp
services.AddProblemDetails(x =>
{
// Control when an exception is included
@@ -98,7 +102,7 @@ In this Project I covered most of important tests like `Unit Testing`, `Integrat
In this app for increasing performance we could use caching mechanism simply with implementing a interface `ICachePolicy<,>` and our object for caching, this will handle with a chancing pipeline on mediateR as cross cutting concern with name of `CachingBehavior`. For example for caching our `FindMovieByIdQuery` query we could use bellow code:
-``` csharp
+```csharp
public class FindMovieByIdQuery : IQuery
{
public int Id { get; init; }
@@ -114,6 +118,7 @@ public class FindMovieByIdQuery : IQuery
}
}
```
+
## Communication with External APIs
In this application for communicating with external apis like [TMDB Apis](https://developers.themoviedb.org/3) and [Youtube Apis](https://developers.google.com/youtube/v3) I used a [Anti Corruption Layer](https://deviq.com/domain-driven-design/anti-corruption-layer) as a [mediator](https://dev.to/asarnaout/the-anti-corruption-layer-pattern-pcd) between our system domain model and external systems domain model. The reason why you might use an anti corruption layer is to create a little padding between subsystems so that they do not `leak into each other` too much.
@@ -127,8 +132,8 @@ In our application I create an anti corruption class for `TMDB api` with name of
In this anti corruption classes we also used some [resiliency mechanisms](https://medium.com/@emanuele.bucarelli/improve-resilience-in-the-net-application-80adda2c7710) like `Retry`, `Circuit-breaker`,`Timeout` and`Bulkhead` patterns for increasing our resiliency in calling third party apis and for doing this I used [Polly](https://github.com/App-vNext/Polly) library.
-
## Technologies - Libraries
+
- ✔️ **[`.NET 6`](https://dotnet.microsoft.com/download)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core
- ✔️ **[`Newtonsoft.Json`](https://github.com/JamesNK/Newtonsoft.Json)** - Json.NET is a popular high-performance JSON framework for .NET
- ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core
@@ -146,14 +151,14 @@ In this anti corruption classes we also used some [resiliency mechanisms](https:
- ✔️ **[`NSubstitute`](https://nsubstitute.github.io/)** - A friendly substitute for .NET mocking libraries.
- ✔️ **[`FluentAssertions`](https://github.com/fluentassertions/fluentassertions)** - A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests.
-
## Configs
+
For using this app we need a [YouTube ApiKey](https://developers.google.com/youtube/v3/getting-started) also for using TMDB api we need a [TMDB Api Key](https://www.themoviedb.org/settings/api) (I put a api key for TMDB in setting file for test purpose). you should set your api keys in [appsettings.json](./src/MovieSearch.Api/appsettings.json) file in bellow section:
-``` json
+```json
"TMDBOptions": {
"BaseApiAddress": "https://api.themoviedb.org/3",
- "ApiKey": "122f0c50ae02fa84601c07025cb6d2f1",
+ "ApiKey": "122f0c50ae02fa84601c07025cb6d2f1",
"Language": "en-US",
"Region": "US"
},
@@ -162,59 +167,61 @@ For using this app we need a [YouTube ApiKey](https://developers.google.com/yout
"SearchPart": "snippet",
"SearchType": "video",
"Order": 4
- },
+ },
```
-We could also set this YouTube ApiKey setting with using OS environments with using bellow command in cmd:
+We could also set this YouTube ApiKey setting with using OS environments with using bellow command in cmd:
-``` cmd
+```cmd
`setx YoutubeVideoOptions__ApiKey "your youtube api key"`
```
-It automatically will replace this env `APiKey` with our appsettings `APIKey`.
+It automatically will replace this env `APiKey` with our appsettings `APIKey`.
Also for security purpose of our Apis I used [API key Authentication](https://codingsonata.com/secure-asp-net-core-web-api-using-api-key-authentication/) and you have to pas a API key in header of request or in query string of your api url with this key name `X-Api-Key`.
For example for url It will be like this:
-``` bash
+```bash
http://localhost:5000/api/v1/movies/150/with-trailers?trailersCount=10&X-Api-Key=C5BFF7F0-B4DF-475E-A331-F737424F013C
```
For setup valid api key there is a class with name [InMemoryGetApiKeyQuery](./src/MovieSearch.Infrastructure/Security/InMemoryGetApiKeyQuery.cs), that this class is a in-memory registry for all valid Api Key. this class implemented `IGetApiKeyQuery` class. you can implement this interface and store your keys in your favorite provider like EF Core Sql Server or a Json file, ...
Some of valid keys for test are:
-``` bash
+```bash
C5BFF7F0-B4DF-475E-A331-F737424F013C
5908D47C-85D3-4024-8C2B-6EC9464398AD
06795D9D-A770-44B9-9B27-03C6ABDB1BAE
```
+
Also our swagger is fully compatible with this Api key and you can authenticate in swagger UI before making any request. Use one of the above keys in this text box.
![](./assets/API-Key-Auth.png)
-
## How to Run
### CMD
+
For running our Apis we need to run bellow command in shell in root of the project.
-``` bash
+```bash
.\scripts\api.bat
```
+
Then after this,our application will be up and running. our API service will be host on http://localhost:5000.
-### Docker Compose
+### Docker Compose
We can run this app on docker with this [docker-compose.yaml](./deployments/docker-compose/docker-compose.yaml) file with bellow command in root of application:
-``` bash
+```bash
docker-compose -f ./deployments/docker-compose/docker-compose.yaml up
```
-Also docker image is available on the docker hub in this address: [https://hub.docker.com/r/mehdihadeli/movie.api](https://hub.docker.com/r/mehdihadeli/movie.api)
+Also docker image is available on the docker hub in this address: [https://hub.docker.com/r/mehdihadeli/movie.api](https://hub.docker.com/r/mehdihadeli/movie.api)
### Kubernetes
For setup your local environment for using kubernetes you can use different approuch but I personally perfer to use [K3s](https://k3s.io/) from rancher team.
-For running our app on kubernetes cluster we should apply [movie-search-api.yaml](./deployments/k8s/movie-search-api.yaml) file with
+For running our app on kubernetes cluster we should apply [movie-search-api.yaml](./deployments/k8s/movie-search-api.yaml) file with
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj
index 19ac9f1..b508d39 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj
@@ -1,32 +1,32 @@
- net7.0
+ net8.0
false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs
index cdf0da3..73757b3 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs
@@ -19,9 +19,8 @@ namespace BuildingBlocks.Test.Factories;
public class CustomApplicationFactory : WebApplicationFactory
where TEntryPoint : class
{
- public CustomApplicationFactory() : this(null)
- {
- }
+ public CustomApplicationFactory()
+ : this(null) { }
public CustomApplicationFactory(Action testRegistrationServices = null)
{
@@ -38,15 +37,18 @@ protected override IHostBuilder CreateHostBuilder()
{
var builder = base.CreateHostBuilder();
- builder = builder.UseSerilog((_, _, configuration) =>
- {
- if (OutputHelper is not null)
- configuration.WriteTo.Xunit(OutputHelper);
+ builder = builder.UseSerilog(
+ (_, _, configuration) =>
+ {
+ if (OutputHelper is not null)
+ configuration.WriteTo.Xunit(OutputHelper);
- configuration.MinimumLevel.Is(LogEventLevel.Information)
- .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
- .WriteTo.Console();
- });
+ configuration
+ .MinimumLevel.Is(LogEventLevel.Information)
+ .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
+ .WriteTo.Console();
+ }
+ );
return builder;
}
@@ -77,16 +79,19 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
using var scope = sp.CreateScope();
var seeders = scope.ServiceProvider.GetServices();
- foreach (var seeder in seeders) seeder.SeedAllAsync().GetAwaiter().GetResult();
+ foreach (var seeder in seeders)
+ seeder.SeedAllAsync().GetAwaiter().GetResult();
});
- builder.UseDefaultServiceProvider((env, c) =>
- {
- // Handling Captive Dependency Problem
- // https://ankitvijay.net/2020/03/17/net-core-and-di-beware-of-captive-dependency/
- // https://blog.ploeh.dk/2014/06/02/captive-dependency/
- if (env.HostingEnvironment.IsEnvironment("test") || env.HostingEnvironment.IsDevelopment())
- c.ValidateScopes = true;
- });
+ builder.UseDefaultServiceProvider(
+ (env, c) =>
+ {
+ // Handling Captive Dependency Problem
+ // https://ankitvijay.net/2020/03/17/net-core-and-di-beware-of-captive-dependency/
+ // https://blog.ploeh.dk/2014/06/02/captive-dependency/
+ if (env.HostingEnvironment.IsEnvironment("test") || env.HostingEnvironment.IsDevelopment())
+ c.ValidateScopes = true;
+ }
+ );
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs
index 1129575..d38c2bd 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs
@@ -81,12 +81,14 @@ public Task ExecuteDbContextAsync(Func> act
return ExecuteScopeAsync(sp => action(sp.GetService(), sp.GetService()));
}
- public Task InsertAsync(params T[] entities) where T : class, IAggregate
+ public Task InsertAsync(params T[] entities)
+ where T : class, IAggregate
{
return ExecuteDbContextAsync(db => db.GetCollection().InsertManyAsync(entities));
}
- public Task InsertAsync(TEntity entity) where TEntity : class, IAggregate
+ public Task InsertAsync(TEntity entity)
+ where TEntity : class, IAggregate
{
return ExecuteDbContextAsync(db => db.GetCollection().InsertOneAsync(entity));
}
@@ -117,8 +119,12 @@ public Task InsertAsync(TEntity entity, TEntity2 en
});
}
- public Task InsertAsync(TEntity entity, TEntity2 entity2,
- TEntity3 entity3, TEntity4 entity4)
+ public Task InsertAsync(
+ TEntity entity,
+ TEntity2 entity2,
+ TEntity3 entity3,
+ TEntity4 entity4
+ )
where TEntity : class, IAggregate
where TEntity2 : class, IAggregate
where TEntity3 : class, IAggregate
@@ -134,10 +140,10 @@ public Task InsertAsync(TEntity entity, T
});
}
- public Task FindAsync(int id) where T : class, IAggregate
+ public Task FindAsync(int id)
+ where T : class, IAggregate
{
- return ExecuteDbContextAsync(db =>
- db.GetCollection().AsQueryable().SingleOrDefaultAsync(x => x.Id == id));
+ return ExecuteDbContextAsync(db => db.GetCollection().AsQueryable().SingleOrDefaultAsync(x => x.Id == id));
}
public override Task InitializeAsync()
@@ -161,16 +167,14 @@ public IntegrationTestFixture()
{
Factory = new CustomApplicationFactory();
}
+
// protected readonly LaunchSettingsFixture LaunchSettings;
protected HttpClient Client => Factory.CreateClient();
+ public IHttpClientFactory HttpClientFactory => ServiceProvider.GetRequiredService();
- public IHttpClientFactory HttpClientFactory =>
- ServiceProvider.GetRequiredService();
-
- public IHttpContextAccessor HttpContextAccessor =>
- ServiceProvider.GetRequiredService();
+ public IHttpContextAccessor HttpContextAccessor => ServiceProvider.GetRequiredService();
public IServiceProvider ServiceProvider => Factory.Services;
public IConfiguration Configuration => Factory.Configuration;
@@ -234,7 +238,8 @@ public Task SendAsync(ICommand request, Cancell
});
}
- public Task SendAsync(T request, CancellationToken cancellationToken) where T : class, ICommand
+ public Task SendAsync(T request, CancellationToken cancellationToken)
+ where T : class, ICommand
{
return ExecuteScopeAsync(sp =>
{
@@ -254,4 +259,4 @@ public Task QueryAsync(IQuery query, Cancellati
return mediator.Send(query, cancellationToken);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs
index 8e441b7..7213b61 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs
@@ -35,4 +35,4 @@ public void Dispose()
{
// ... clean up
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs
index c698902..f62d9b9 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs
@@ -34,4 +34,4 @@ public void Dispose()
{
_mongoRunner.Dispose();
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs
index 88bc613..2f968e7 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs
@@ -21,4 +21,4 @@ public void Dispose()
var client = new MongoClient(MongoOptions.ConnectionString);
client.DropDatabase(MongoOptions.DatabaseName);
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs
index f8a68cc..aea6815 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs
@@ -13,12 +13,13 @@ public abstract class WebApiTestFixture : IntegrationTe
where TEntryPoint : class
where TDbContext : class, IMongoDbContext
{
- private static readonly JsonSerializerOptions SerializerOptions = new()
- {
- PropertyNameCaseInsensitive = true,
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- Converters = {new JsonStringEnumConverter()}
- };
+ private static readonly JsonSerializerOptions SerializerOptions =
+ new()
+ {
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters = { new JsonStringEnumConverter() }
+ };
private string _route;
@@ -30,9 +31,11 @@ protected void SetPath(string route)
return;
}
- if (route.StartsWith("/")) route = route.Substring(1, route.Length - 1);
+ if (route.StartsWith("/"))
+ route = route.Substring(1, route.Length - 1);
- if (route.EndsWith("/")) route = route.Substring(0, route.Length - 1);
+ if (route.EndsWith("/"))
+ route = route.Substring(0, route.Length - 1);
_route = $"{route}/";
}
@@ -104,19 +107,19 @@ protected static async Task ReadAsync(HttpResponseMessage response)
return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync(), SerializerOptions);
}
- protected virtual void ConfigureServices(IServiceCollection services)
- {
- }
+ protected virtual void ConfigureServices(IServiceCollection services) { }
}
-public abstract class WebApiTestFixture : IntegrationTestFixture where TEntryPoint : class
+public abstract class WebApiTestFixture : IntegrationTestFixture
+ where TEntryPoint : class
{
- private static readonly JsonSerializerOptions SerializerOptions = new()
- {
- PropertyNameCaseInsensitive = true,
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- Converters = {new JsonStringEnumConverter()}
- };
+ private static readonly JsonSerializerOptions SerializerOptions =
+ new()
+ {
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters = { new JsonStringEnumConverter() }
+ };
private string _route;
@@ -128,9 +131,11 @@ protected void SetPath(string route)
return;
}
- if (route.StartsWith("/")) route = route.Substring(1, route.Length - 1);
+ if (route.StartsWith("/"))
+ route = route.Substring(1, route.Length - 1);
- if (route.EndsWith("/")) route = route.Substring(0, route.Length - 1);
+ if (route.EndsWith("/"))
+ route = route.Substring(0, route.Length - 1);
_route = $"{route}/";
}
@@ -202,7 +207,5 @@ protected static async Task ReadAsync(HttpResponseMessage response)
return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync(), SerializerOptions);
}
- protected virtual void ConfigureServices(IServiceCollection services)
- {
- }
-}
\ No newline at end of file
+ protected virtual void ConfigureServices(IServiceCollection services) { }
+}
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs
index 2b26461..5ce8e91 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs
@@ -10,18 +10,13 @@ public static TSettings GetOptions(string section, string settingsFil
settingsFileName ??= "appsettings.test.json";
var configuration = new TSettings();
- GetConfigurationRoot(settingsFileName)
- .GetSection(section)
- .Bind(configuration);
+ GetConfigurationRoot(settingsFileName).GetSection(section).Bind(configuration);
return configuration;
}
private static IConfigurationRoot GetConfigurationRoot(string settingsFileName)
{
- return new ConfigurationBuilder()
- .AddJsonFile(settingsFileName, true)
- .AddEnvironmentVariables()
- .Build();
+ return new ConfigurationBuilder().AddJsonFile(settingsFileName, true).AddEnvironmentVariables().Build();
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs
index e6ce3aa..4bdd695 100644
--- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs
+++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs
@@ -9,4 +9,4 @@ public Task SeedAllAsync()
{
return Task.CompletedTask;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj
index 6cf5b0b..94e257c 100644
--- a/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj
+++ b/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj
@@ -1,62 +1,60 @@
- net7.0
+ net8.0
enable
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs b/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs
index e692c06..bde26ec 100644
--- a/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs
@@ -14,4 +14,4 @@ public static string With(Type ownerType, params string[] keys)
{
return With($"{ownerType.GetCacheKey()}:{string.Join("-", keys)}");
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs
index 1de6eb6..4e3f8a1 100644
--- a/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs
@@ -18,18 +18,22 @@ public class CachingBehavior : IPipelineBehavior> _logger;
private readonly int defaultCacheExpirationInHours = 1;
-
- public CachingBehavior(IEasyCachingProviderFactory cachingFactory,
+ public CachingBehavior(
+ IEasyCachingProviderFactory cachingFactory,
ILogger> logger,
- IEnumerable> cachePolicies)
+ IEnumerable> cachePolicies
+ )
{
_logger = logger;
_cachingProvider = cachingFactory.GetCachingProvider("mem");
_cachePolicies = cachePolicies;
}
- public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken
- cancellationToken)
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken
+ )
{
var cachePolicy = _cachePolicies.FirstOrDefault();
if (cachePolicy == null)
@@ -40,20 +44,25 @@ public async Task Handle(TRequest request, RequestHandlerDelegate(cacheKey);
if (cachedResponse.Value != null)
{
- _logger.LogDebug("Response retrieved {TRequest} from cache. CacheKey: {CacheKey}",
- typeof(TRequest).FullName, cacheKey);
+ _logger.LogDebug(
+ "Response retrieved {TRequest} from cache. CacheKey: {CacheKey}",
+ typeof(TRequest).FullName,
+ cacheKey
+ );
return cachedResponse.Value;
}
var response = await next();
- var time = cachePolicy.AbsoluteExpirationRelativeToNow ??
- DateTime.Now.AddHours(defaultCacheExpirationInHours);
+ var time = cachePolicy.AbsoluteExpirationRelativeToNow ?? DateTime.Now.AddHours(defaultCacheExpirationInHours);
await _cachingProvider.SetAsync(cacheKey, response, time.TimeOfDay);
- _logger.LogDebug("Caching response for {TRequest} with cache key: {CacheKey}", typeof(TRequest).FullName,
- cacheKey);
+ _logger.LogDebug(
+ "Caching response for {TRequest} with cache key: {CacheKey}",
+ typeof(TRequest).FullName,
+ cacheKey
+ );
return response;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs b/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs
index 29f2a2d..a3f1d36 100644
--- a/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs
@@ -10,4 +10,4 @@ public ExpirationOptions(DateTimeOffset absoluteExpiration)
}
public DateTimeOffset AbsoluteExpiration { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs
index 8397f50..4abb240 100644
--- a/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs
@@ -7,25 +7,27 @@ namespace BuildingBlocks.Caching;
public static class Extensions
{
- public static IServiceCollection AddCachingRequestPolicies(this IServiceCollection services,
- IList assembliesToScan)
+ public static IServiceCollection AddCachingRequestPolicies(
+ this IServiceCollection services,
+ IList assembliesToScan
+ )
{
// ICachePolicy discovery and registration
- services.Scan(scan => scan
- .FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies())
- .AddClasses(classes => classes.AssignableTo(typeof(ICachePolicy<,>)),
- false)
- .AsImplementedInterfaces()
- .WithTransientLifetime());
+ services.Scan(scan =>
+ scan.FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies())
+ .AddClasses(classes => classes.AssignableTo(typeof(ICachePolicy<,>)), false)
+ .AsImplementedInterfaces()
+ .WithTransientLifetime()
+ );
// IInvalidateCachePolicy discovery and registration
- services.Scan(scan => scan
- .FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies())
- .AddClasses(classes => classes.AssignableTo(typeof(IInvalidateCachePolicy<,>)),
- false)
- .AsImplementedInterfaces()
- .WithTransientLifetime());
+ services.Scan(scan =>
+ scan.FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies())
+ .AddClasses(classes => classes.AssignableTo(typeof(IInvalidateCachePolicy<,>)), false)
+ .AsImplementedInterfaces()
+ .WithTransientLifetime()
+ );
return services;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs b/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs
index 92f3844..0ea4efa 100644
--- a/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs
@@ -4,14 +4,15 @@
namespace BuildingBlocks.Caching;
-public interface ICachePolicy where TRequest : IRequest
+public interface ICachePolicy
+ where TRequest : IRequest
{
DateTime? AbsoluteExpirationRelativeToNow { get; }
string GetCacheKey(TRequest request)
{
- var r = new {request};
+ var r = new { request };
var props = r.request.GetType().GetProperties().Select(pi => $"{pi.Name}:{pi.GetValue(r.request, null)}");
return $"{typeof(TRequest).FullName}{{{string.Join(",", props)}}}";
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs b/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs
index 8a0fa72..0b8e922 100644
--- a/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs
@@ -3,17 +3,16 @@
namespace BuildingBlocks.Caching;
-public interface IInvalidateCachePolicy where TRequest : IRequest
+public interface IInvalidateCachePolicy
+ where TRequest : IRequest
{
string GetCacheKey(TRequest request)
{
- var r = new {request};
+ var r = new { request };
var props = r.request.GetType().GetProperties().Select(pi => $"{pi.Name}:{pi.GetValue(r.request, null)}");
return $"{typeof(TRequest).FullName}{{{string.Join(",", props)}}}";
}
}
public interface IInvalidateCachePolicy : IInvalidateCachePolicy
- where TRequest : IRequest
-{
-}
\ No newline at end of file
+ where TRequest : IRequest { }
diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs
index 5f3c000..440992f 100644
--- a/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs
@@ -16,18 +16,22 @@ public class InvalidateCachingBehavior : IPipelineBehavior<
private readonly IEnumerable> _invalidateCachePolicies;
private readonly ILogger> _logger;
-
- public InvalidateCachingBehavior(IEasyCachingProviderFactory cachingFactory,
+ public InvalidateCachingBehavior(
+ IEasyCachingProviderFactory cachingFactory,
ILogger> logger,
- IEnumerable> invalidateCachingPolicies)
+ IEnumerable> invalidateCachingPolicies
+ )
{
_logger = logger;
_cachingProvider = cachingFactory.GetCachingProvider("mem");
_invalidateCachePolicies = invalidateCachingPolicies;
}
- public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken
- cancellationToken)
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken
+ )
{
var cachePolicy = _invalidateCachePolicies.FirstOrDefault();
if (cachePolicy == null)
@@ -43,4 +47,4 @@ public async Task Handle(TRequest request, RequestHandlerDelegate : IRequest where T : notnull
-{
-}
+public interface ICommand : IRequest
+ where T : notnull { }
-public interface ICommand : IRequest
-{
-}
+public interface ICommand : IRequest { }
public interface IQuery : IRequest
- where T : notnull
-{
-}
+ where T : notnull { }
public interface ICreateCommand : ICommand, ITxRequest
- where TResponse : notnull
-{
-}
+ where TResponse : notnull { }
-public interface ICreateCommand : ICommand, ITxRequest
-{
-}
+public interface ICreateCommand : ICommand, ITxRequest { }
-public interface IUpdateCommand : ICommand, ITxRequest
-{
-}
+public interface IUpdateCommand : ICommand, ITxRequest { }
public interface IUpdateCommand : ICommand, ITxRequest
- where TResponse : notnull
-{
-}
+ where TResponse : notnull { }
public interface IDeleteCommand : ICommand
where TId : struct
@@ -43,7 +30,8 @@ public interface IDeleteCommand : ICommand
public TId Id { get; init; }
}
-public interface IDeleteCommand : ICommand where TId : struct
+public interface IDeleteCommand : ICommand
+ where TId : struct
{
public TId Id { get; init; }
}
@@ -58,9 +46,7 @@ public interface IPageList
}
public interface IListQuery : IQuery, IPageList
- where TResponse : notnull
-{
-}
+ where TResponse : notnull { }
public interface IItemQuery : IQuery
where TId : struct
@@ -72,19 +58,18 @@ public interface IItemQuery : IQuery
public record FilterModel(string FieldName, string Comparision, string FieldValue);
-public record ListResultModel(List Items, long TotalItems, int Page, int PageSize) where T : notnull
+public record ListResultModel(List Items, long TotalItems, int Page, int PageSize)
+ where T : notnull
{
public static ListResultModel Empty => new(Enumerable.Empty().ToList(), 0, 0, 0);
- public static ListResultModel Create(List items, long totalItems = 0, int page = default,
- int pageSize = 20)
+ public static ListResultModel Create(List items, long totalItems = 0, int page = default, int pageSize = 20)
{
return new ListResultModel(items, totalItems, page, pageSize);
}
public ListResultModel Map(Func map)
{
- return ListResultModel.Create(
- Items.Select(map).ToList(), TotalItems, Page, PageSize);
+ return ListResultModel.Create(Items.Select(map).ToList(), TotalItems, Page, PageSize);
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs b/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs
index d4a1c6e..d6ba884 100644
--- a/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs
@@ -1,10 +1,8 @@
namespace BuildingBlocks.Domain;
-public interface IAggregate : IAggregate
-{
-}
+public interface IAggregate : IAggregate { }
public interface IAggregate
{
public T Id { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs b/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs
index aa2295c..9884ac8 100644
--- a/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs
@@ -1,5 +1,3 @@
namespace BuildingBlocks.Domain;
-public interface ITxRequest
-{
-}
\ No newline at end of file
+public interface ITxRequest { }
diff --git a/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs b/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs
index 134a590..9f28424 100644
--- a/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs
+++ b/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs
@@ -36,17 +36,14 @@ private TContext Create(string basePath, string environmentName)
var connstr = config.GetConnectionString("DefaultConnection");
if (string.IsNullOrWhiteSpace(connstr))
- throw new InvalidOperationException(
- "Could not find a connection string named 'Default'.");
+ throw new InvalidOperationException("Could not find a connection string named 'Default'.");
return Create(connstr);
}
private TContext Create(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
- throw new ArgumentException(
- $"{nameof(connectionString)} is null or empty.",
- nameof(connectionString));
+ throw new ArgumentException($"{nameof(connectionString)} is null or empty.", nameof(connectionString));
var optionsBuilder = new DbContextOptionsBuilder();
@@ -57,4 +54,4 @@ private TContext Create(string connectionString)
var options = optionsBuilder.Options;
return CreateNewInstance(options);
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs b/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs
index 5df3402..9c567ca 100644
--- a/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs
@@ -8,24 +8,29 @@ namespace BuildingBlocks.EFCore;
public static class QueryableExtensions
{
- public static async Task> PaginateAsync(this IQueryable collection,
- IPageList query)
+ public static async Task> PaginateAsync(this IQueryable collection, IPageList query)
{
return await collection.PaginateAsync(query.Page, query.PageSize);
}
- public static async Task> PaginateAsync(this IQueryable collection, int page = 1, int
- pageSize = 10)
+ public static async Task> PaginateAsync(
+ this IQueryable collection,
+ int page = 1,
+ int pageSize = 10
+ )
{
- if (page <= 0) page = 1;
+ if (page <= 0)
+ page = 1;
- if (pageSize <= 0) pageSize = 10;
+ if (pageSize <= 0)
+ pageSize = 10;
var isEmpty = await collection.AnyAsync() == false;
- if (isEmpty) return ListResultModel.Empty;
+ if (isEmpty)
+ return ListResultModel.Empty;
var totalItems = await collection.CountAsync();
- var totalPages = (int) Math.Ceiling((decimal) totalItems / pageSize);
+ var totalPages = (int)Math.Ceiling((decimal)totalItems / pageSize);
var data = await collection.Limit(page, pageSize).ToListAsync();
return ListResultModel.Create(data, totalItems, page, pageSize);
@@ -36,17 +41,17 @@ public static IQueryable Limit(this IQueryable collection, IPageList qu
return collection.Limit(query.Page, query.PageSize);
}
- public static IQueryable Limit(this IQueryable collection,
- int page = 1, int resultsPerPage = 10)
+ public static IQueryable Limit(this IQueryable collection, int page = 1, int resultsPerPage = 10)
{
- if (page <= 0) page = 1;
+ if (page <= 0)
+ page = 1;
- if (resultsPerPage <= 0) resultsPerPage = 10;
+ if (resultsPerPage <= 0)
+ resultsPerPage = 10;
var skip = (page - 1) * resultsPerPage;
- var data = collection.Skip(skip)
- .Take(resultsPerPage);
+ var data = collection.Skip(skip).Take(resultsPerPage);
return data;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs
index e390fa7..a6b4dec 100644
--- a/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs
@@ -5,16 +5,11 @@ namespace BuildingBlocks.Exception;
public class ApiException : ApplicationException
{
- public ApiException()
- {
- }
+ public ApiException() { }
- public ApiException(string message) : base(message)
- {
- }
+ public ApiException(string message)
+ : base(message) { }
public ApiException(string message, params object[] args)
- : base(string.Format(CultureInfo.CurrentCulture, message, args))
- {
- }
-}
\ No newline at end of file
+ : base(string.Format(CultureInfo.CurrentCulture, message, args)) { }
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs
index 67629f8..88eac6e 100644
--- a/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs
@@ -2,10 +2,11 @@ namespace BuildingBlocks.Exception;
public class AppException : System.Exception
{
- public AppException(string message, string code = default!) : base(message)
+ public AppException(string message, string code = default!)
+ : base(message)
{
Code = code;
}
public virtual string Code { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs
index 9876e74..52ae106 100644
--- a/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs
@@ -4,7 +4,6 @@ namespace BuildingBlocks.Exception;
public class BadRequestException : ApplicationException
{
- public BadRequestException(string message) : base(message)
- {
- }
-}
\ No newline at end of file
+ public BadRequestException(string message)
+ : base(message) { }
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs
index 04670a3..673af08 100644
--- a/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs
@@ -10,4 +10,4 @@ public InvalidCommandException(List errors)
}
public List Errors { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs
index c9a65ae..219e2ea 100644
--- a/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs
@@ -2,7 +2,6 @@ namespace BuildingBlocks.Exception;
public class NotFoundException : System.Exception
{
- public NotFoundException(string message) : base(message)
- {
- }
-}
\ No newline at end of file
+ public NotFoundException(string message)
+ : base(message) { }
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs
index b8398a4..4a64ef4 100644
--- a/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs
@@ -13,57 +13,67 @@ public static class Extensions
{
public static WebApplicationBuilder AddCustomSerilog(
this WebApplicationBuilder builder,
- Action? extraConfigure = null)
+ Action? extraConfigure = null
+ )
{
- builder.Host.UseSerilog((context, serviceProvider, loggerConfiguration) =>
- {
- var loggerOptions = context.Configuration.GetSection(nameof(LoggerOptions)).Get();
+ builder.Host.UseSerilog(
+ (context, serviceProvider, loggerConfiguration) =>
+ {
+ var loggerOptions = context.Configuration.GetSection(nameof(LoggerOptions)).Get();
- extraConfigure?.Invoke(loggerConfiguration);
+ extraConfigure?.Invoke(loggerConfiguration);
- loggerConfiguration
- .ReadFrom.Configuration(context.Configuration)
- .ReadFrom.Services(serviceProvider)
- .Enrich.WithExceptionDetails()
- .Enrich.WithCorrelationId()
- .Enrich.FromLogContext();
+ loggerConfiguration
+ .ReadFrom.Configuration(context.Configuration)
+ .ReadFrom.Services(serviceProvider)
+ .Enrich.WithExceptionDetails()
+ .Enrich.WithCorrelationId()
+ .Enrich.FromLogContext();
- var level = Enum.TryParse(loggerOptions?.Level, true, out var logLevel)
- ? logLevel
- : LogEventLevel.Information;
+ var level = Enum.TryParse(loggerOptions?.Level, true, out var logLevel)
+ ? logLevel
+ : LogEventLevel.Information;
- // https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/
- loggerConfiguration.MinimumLevel.Is(level)
- .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
- .MinimumLevel
- // Filter out ASP.NET Core infrastructure logs that are Information and below
- .Override("Microsoft.AspNetCore", LogEventLevel.Warning);
+ // https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/
+ loggerConfiguration
+ .MinimumLevel.Is(level)
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
+ .MinimumLevel
+ // Filter out ASP.NET Core infrastructure logs that are Information and below
+ .Override("Microsoft.AspNetCore", LogEventLevel.Warning);
- if (context.HostingEnvironment.IsDevelopment())
- {
- loggerConfiguration.WriteTo.Async(writeTo => writeTo.SpectreConsole(
- loggerOptions?.LogTemplate ??
- "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}",
- level));
- }
- else
- {
- if (!string.IsNullOrEmpty(loggerOptions?.ElasticSearchUrl))
- loggerConfiguration.WriteTo.Async(writeTo => writeTo.Elasticsearch(loggerOptions.ElasticSearchUrl));
+ if (context.HostingEnvironment.IsDevelopment())
+ {
+ loggerConfiguration.WriteTo.Async(writeTo =>
+ writeTo.SpectreConsole(
+ loggerOptions?.LogTemplate
+ ?? "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}",
+ level
+ )
+ );
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(loggerOptions?.ElasticSearchUrl))
+ loggerConfiguration.WriteTo.Async(writeTo =>
+ writeTo.Elasticsearch(loggerOptions.ElasticSearchUrl)
+ );
- if (!string.IsNullOrEmpty(loggerOptions?.SeqUrl))
- loggerConfiguration.WriteTo.Async(writeTo => writeTo.Seq(loggerOptions.SeqUrl));
- }
+ if (!string.IsNullOrEmpty(loggerOptions?.SeqUrl))
+ loggerConfiguration.WriteTo.Async(writeTo => writeTo.Seq(loggerOptions.SeqUrl));
+ }
- if (!string.IsNullOrEmpty(loggerOptions?.LogPath))
- loggerConfiguration.WriteTo.File(
- loggerOptions.LogPath,
- outputTemplate: loggerOptions.LogTemplate ??
- "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}",
- rollingInterval: RollingInterval.Day,
- rollOnFileSizeLimit: true);
- });
+ if (!string.IsNullOrEmpty(loggerOptions?.LogPath))
+ loggerConfiguration.WriteTo.File(
+ loggerOptions.LogPath,
+ outputTemplate: loggerOptions.LogTemplate
+ ?? "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}",
+ rollingInterval: RollingInterval.Day,
+ rollOnFileSizeLimit: true
+ );
+ }
+ );
return builder;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs b/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs
index 6139181..8edb8af 100644
--- a/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs
@@ -7,4 +7,4 @@ public class LoggerOptions
public string? ElasticSearchUrl { get; set; }
public string? LogTemplate { get; set; }
public string? LogPath { get; set; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs
index 25b01e8..707d269 100644
--- a/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs
@@ -17,13 +17,20 @@ public LoggingBehavior(ILogger> logger)
_logger = logger;
}
- public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken
- cancellationToken)
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken
+ )
{
const string prefix = nameof(LoggingBehavior);
- _logger.LogInformation("[{Prefix}] Handle request={X-RequestData} and response={X-ResponseData}",
- prefix, typeof(TRequest).Name, typeof(TResponse).Name);
+ _logger.LogInformation(
+ "[{Prefix}] Handle request={X-RequestData} and response={X-ResponseData}",
+ prefix,
+ typeof(TRequest).Name,
+ typeof(TResponse).Name
+ );
var timer = new Stopwatch();
timer.Start();
@@ -33,10 +40,14 @@ public async Task Handle(TRequest request, RequestHandlerDelegate 3) // if the request is greater than 3 seconds, then log the warnings
- _logger.LogWarning("[{Perf-Possible}] The request {X-RequestData} took {TimeTaken} seconds",
- prefix, typeof(TRequest).Name, timeTaken.Seconds);
+ _logger.LogWarning(
+ "[{Perf-Possible}] The request {X-RequestData} took {TimeTaken} seconds",
+ prefix,
+ typeof(TRequest).Name,
+ timeTaken.Seconds
+ );
_logger.LogInformation("[{Prefix}] Handled {X-RequestData}", prefix, typeof(TRequest).Name);
return response;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs
index 94beec9..a34013b 100644
--- a/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs
@@ -9,16 +9,22 @@ public static class Extensions
private const string SectionName = "Mongo";
public static IServiceCollection AddMongoDbContext(
- this IServiceCollection services, IConfiguration configuration,
- string sectionName = SectionName, Action optionsAction = null)
+ this IServiceCollection services,
+ IConfiguration configuration,
+ string sectionName = SectionName,
+ Action optionsAction = null
+ )
where TContext : MongoDbContext
{
return services.AddMongoDbContext(configuration, sectionName, optionsAction);
}
public static IServiceCollection AddMongoDbContext(
- this IServiceCollection services, IConfiguration configuration,
- string sectionName = SectionName, Action optionsAction = null)
+ this IServiceCollection services,
+ IConfiguration configuration,
+ string sectionName = SectionName,
+ Action optionsAction = null
+ )
where TContextService : IMongoDbContext
where TContextImplementation : MongoDbContext, TContextService
{
@@ -37,4 +43,4 @@ public static IServiceCollection AddMongoDbContext GetCollection();
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs
index d31410b..a7fad34 100644
--- a/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs
@@ -16,9 +16,7 @@ public class ImmutablePocoConvention : ConventionBase, IClassMapConvention
private readonly BindingFlags _bindingFlags;
public ImmutablePocoConvention()
- : this(BindingFlags.Instance | BindingFlags.Public)
- {
- }
+ : this(BindingFlags.Instance | BindingFlags.Public) { }
public ImmutablePocoConvention(BindingFlags bindingFlags)
{
@@ -27,7 +25,8 @@ public ImmutablePocoConvention(BindingFlags bindingFlags)
public void Apply(BsonClassMap classMap)
{
- var readOnlyProperties = classMap.ClassType.GetTypeInfo()
+ var readOnlyProperties = classMap
+ .ClassType.GetTypeInfo()
.GetProperties(_bindingFlags)
.Where(p => IsReadOnlyProperty(classMap, p))
.ToList();
@@ -48,8 +47,7 @@ public void Apply(BsonClassMap classMap)
}
}
- private static List GetMatchingProperties(ConstructorInfo constructor,
- List properties)
+ private static List GetMatchingProperties(ConstructorInfo constructor, List properties)
{
var matchProperties = new List();
@@ -66,11 +64,10 @@ private static List GetMatchingProperties(ConstructorInfo construc
return matchProperties;
}
-
private static bool ParameterMatchProperty(ParameterInfo parameter, PropertyInfo property)
{
return string.Equals(property.Name, parameter.Name, StringComparison.InvariantCultureIgnoreCase)
- && parameter.ParameterType == property.PropertyType;
+ && parameter.ParameterType == property.PropertyType;
}
private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo propertyInfo)
@@ -94,4 +91,4 @@ private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo prope
return true;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs
index 42d0ad8..6c1de0f 100644
--- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs
@@ -61,13 +61,17 @@ public async Task RollbackTransactionAsync()
private static void RegisterConventions()
{
- ConventionRegistry.Register("conventions", new ConventionPack
- {
- new CamelCaseElementNameConvention(),
- new IgnoreExtraElementsConvention(true),
- new EnumRepresentationConvention(BsonType.String),
- new IgnoreIfDefaultConvention(true),
- new ImmutablePocoConvention()
- }, _ => true);
+ ConventionRegistry.Register(
+ "conventions",
+ new ConventionPack
+ {
+ new CamelCaseElementNameConvention(),
+ new IgnoreExtraElementsConvention(true),
+ new EnumRepresentationConvention(BsonType.String),
+ new IgnoreIfDefaultConvention(true),
+ new ImmutablePocoConvention()
+ },
+ _ => true
+ );
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs
index cb70b60..d96f562 100644
--- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs
@@ -7,4 +7,4 @@ public class MongoOptions
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
public static Guid UniqueId { get; set; } = Guid.NewGuid();
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs
index da9a763..7db9601 100644
--- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs
@@ -8,24 +8,29 @@ namespace MicroBootstrap.Mongo;
public static class MongoQueryableExtensions
{
- public static async Task> PaginateAsync(this IMongoQueryable collection,
- IPageList query)
+ public static async Task> PaginateAsync(this IMongoQueryable collection, IPageList query)
{
return await collection.PaginateAsync(query.Page, query.PageSize);
}
- public static async Task> PaginateAsync(this IMongoQueryable collection, int page = 1,
- int pageSize = 10)
+ public static async Task> PaginateAsync(
+ this IMongoQueryable collection,
+ int page = 1,
+ int pageSize = 10
+ )
{
- if (page <= 0) page = 1;
+ if (page <= 0)
+ page = 1;
- if (pageSize <= 0) pageSize = 10;
+ if (pageSize <= 0)
+ pageSize = 10;
var isEmpty = await collection.AnyAsync() == false;
- if (isEmpty) return ListResultModel.Empty;
+ if (isEmpty)
+ return ListResultModel.Empty;
var totalItems = await collection.CountAsync();
- var totalPages = (int) Math.Ceiling((decimal) totalItems / pageSize);
+ var totalPages = (int)Math.Ceiling((decimal)totalItems / pageSize);
var data = await collection.Limit(page, pageSize).ToListAsync();
return ListResultModel.Create(data, totalItems, page, pageSize);
@@ -36,17 +41,17 @@ public static IMongoQueryable Limit(this IMongoQueryable collection, IP
return collection.Limit(query.Page, query.PageSize);
}
- public static IMongoQueryable Limit(this IMongoQueryable collection,
- int page = 1, int resultsPerPage = 10)
+ public static IMongoQueryable Limit(this IMongoQueryable collection, int page = 1, int resultsPerPage = 10)
{
- if (page <= 0) page = 1;
+ if (page <= 0)
+ page = 1;
- if (resultsPerPage <= 0) resultsPerPage = 10;
+ if (resultsPerPage <= 0)
+ resultsPerPage = 10;
var skip = (page - 1) * resultsPerPage;
- var data = collection.Skip(skip)
- .Take(resultsPerPage);
+ var data = collection.Skip(skip).Take(resultsPerPage);
return data;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs
index 5670ef7..b87d35e 100644
--- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs
@@ -33,7 +33,6 @@ public Task GetAsync(Expression> predicate)
return DbSet.Find(predicate).SingleOrDefaultAsync();
}
-
public async Task> FindAsync(Expression> predicate)
{
return await DbSet.Find(predicate).ToListAsync();
@@ -44,8 +43,8 @@ public IAsyncEnumerable GetAllAsync(Expression> pre
return DbSet.AsQueryable().ToAsyncEnumerable();
}
- public Task> BrowseAsync(Expression> predicate,
- TQuery query) where TQuery : IPageList
+ public Task> BrowseAsync(Expression> predicate, TQuery query)
+ where TQuery : IPageList
{
return DbSet.AsQueryable().Where(predicate).PaginateAsync(query);
}
@@ -84,4 +83,4 @@ public void Dispose()
{
_context?.Dispose();
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs
index f5f232c..0c0ee82 100644
--- a/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs
+++ b/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs
@@ -23,17 +23,31 @@ public TxBehavior(IMongoDbContext dbContext, ILogger Handle(TRequest request, RequestHandlerDelegate next, CancellationToken
- cancellationToken)
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken
+ )
{
- if (request is not ITxRequest) return await next();
+ if (request is not ITxRequest)
+ return await next();
- _logger.LogInformation("{Prefix} Handled command {MediatRRequest}", nameof(TxBehavior),
- typeof(TRequest).FullName);
- _logger.LogDebug("{Prefix} Handled command {MediatRRequest} with content {RequestContent}",
- nameof(TxBehavior), typeof(TRequest).FullName, JsonSerializer.Serialize(request));
- _logger.LogInformation("{Prefix} Open the transaction for {MediatRRequest}",
- nameof(TxBehavior), typeof(TRequest).FullName);
+ _logger.LogInformation(
+ "{Prefix} Handled command {MediatRRequest}",
+ nameof(TxBehavior),
+ typeof(TRequest).FullName
+ );
+ _logger.LogDebug(
+ "{Prefix} Handled command {MediatRRequest} with content {RequestContent}",
+ nameof(TxBehavior),
+ typeof(TRequest).FullName,
+ JsonSerializer.Serialize(request)
+ );
+ _logger.LogInformation(
+ "{Prefix} Open the transaction for {MediatRRequest}",
+ nameof(TxBehavior),
+ typeof(TRequest).FullName
+ );
try
{
@@ -41,8 +55,11 @@ public async Task Handle(TRequest request, RequestHandlerDelegate), typeof(TRequest).FullName);
+ _logger.LogInformation(
+ "{Prefix} Executed the {MediatRRequest} request",
+ nameof(TxBehavior),
+ typeof(TRequest).FullName
+ );
await _dbContext.CommitTransactionAsync();
@@ -54,4 +71,4 @@ public async Task Handle(TRequest request, RequestHandlerDelegate : IDisposable where TEntity : class, IAggregate
+public interface IRepository : IDisposable
+ where TEntity : class, IAggregate
{
Task GetAsync(Guid id);
Task GetAsync(Expression> predicate);
Task> FindAsync(Expression> predicate);
IAsyncEnumerable GetAllAsync(Expression> predicate = null);
- public Task> BrowseAsync(Expression