diff --git a/.editorconfig b/.editorconfig index e516760f2b96a..596c802dc93de 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,7 @@ root = true insert_final_newline = true indent_style = space indent_size = 4 +trim_trailing_whitespace = true [project.json] indent_size = 2 @@ -49,7 +50,7 @@ dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected dotnet_naming_style.static_prefix_style.required_prefix = s_ -dotnet_naming_style.static_prefix_style.capitalization = camel_case +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 @@ -58,7 +59,7 @@ dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_ 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 +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case # Code quality dotnet_style_readonly_field = true:suggestion @@ -199,5 +200,11 @@ indent_size = 2 # Shell scripts [*.sh] end_of_line = lf + [*.{cmd, bat}] end_of_line = crlf + +# Markdown files +[*.md] + # Double trailing spaces can be used for BR tags, and other instances are enforced by Markdownlint +trim_trailing_whitespace = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6b372265ce99c..55c638891605a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,218 +1,3 @@ -# Lines starting with '#' are comments. -# Each line is a file pattern followed by one or more owners. +# DO NOT OVERWRITE THIS EMPTY FILE. -# More details are here: https://help.github.com/articles/about-codeowners/ - -# The '*' pattern is global owners. - -* @dotnet/docs - -# Order is important. The last matching pattern has the most precedence. -# The folders are ordered as follows: -# . Guides -# . .NET Core sections -# . .NET Framework sections -# . .NET Standard -# . Architecture e-books - -# In each subsection, folders are order first by depth, then alphabetically. -# This should make it easy to add new rules without breaking existing ones. - -######## API landing page ########### -/api/ @dotnet/docs - -############ What's new ############### -/docs/whats-new/ @billwagner - -############ Guides ################ -# .NET Core -/docs/core/ @dotnet/docs - -# .NET Framework -/docs/framework/ @dotnet/docs - -# .NET Standard -/docs/standard/ @gewarren - -# The C# Guide: -/docs/csharp/ @BillWagner - -# The F# Guide: -/docs/fsharp/ @dotnet/docs @kathleendollard - -# The Visual Basic Guide: -/docs/visual-basic/ @KathleenDollard @BillWagner - -# The ML.NET Guide: -/docs/machine-learning/ @luisquintanilla - -# The .NET for Apache Spark Guide: -/docs/spark/ @luisquintanilla - -# The .NET Architecture Guide: -/docs/architecture/ @nishanil @IEvangelist - -# .NET Orleans -/docs/orleans/ @IEvangelist - -# Azure Guide -/docs/azure/ @CamSoper - -# Azure SDK package list -/docs/azure/includes/ @dotnet/docs - -# IoT Guide -/docs/iot/ @CamSoper - -# Includes files -/includes/ @dotnet/docs - -############### .NET 5 ################ - -# Code analysis -/docs/fundamentals/code-analysis/ @gewarren - -############### .NET Core ######################## -# What's New -/docs/core/whats-new/ @adegeo -# Deployment -/docs/core/deploying/ @adegeo -# Diagnostics -/docs/core/diagnostics/ @tommcdon @dotnet/docs -# Extensions -/docs/core/extensions/ @IEvangelist -# Docker -/docs/core/docker/ @IEvangelist -# Install -/docs/core/install/ @adegeo -# Breaking changes -/docs/core/compatibility/ @gewarren -# Project SDKs -/docs/core/project-sdk/ @gewarren -# Config settings -/docs/core/runtime-config/ @gewarren -# Testing -/docs/core/testing/ @IEvangelist -# Tools -/docs/core/tools/ @tdykstra -# Tutorials -/docs/core/tutorials/ @tdykstra - -################### .NET Framework ################## -# App domains -/docs/framework/app-domains/ @gewarren -# Config - WinForms -/docs/framework/configure-apps/file-schema/winforms/ @adegeo -# Deployment -/docs/framework/deployment/ @adegeo -# Installation -/docs/framework/install/ @adegeo -# Migration guide -/docs/framework/migration-guide/ @gewarren -# Reflection -/docs/framework/reflection-and-codedom/ @adegeo -# Resources -/docs/framework/resources/ @adegeo -# Tools -/docs/framework/tools/ @gewarren -# UI Automation -/docs/framework/ui-automation/ @adegeo -# What's New -/docs/framework/whats-new/ @gewarren -# Winforms -/docs/framework/winforms/ @adegeo -# WPF -/docs/framework/wpf/ @adegeo -# XAML Services -/docs/framework/xaml-services/ @adegeo -# ADO.NET -/docs/framework/data/ @mcleblanc -# NCL -/docs/framework/configure-apps/file-schema/network/ @karelz -/docs/framework/network-programming/ @karelz -# WCF -/docs/framework/configure-apps/file-schema/wcf/ @HongGit -/docs/framework/wcf/ @HongGit - -################## .NET Standard ################## -# Analyzers -/docs/standard/analyzers/ @IEvangelist -# Assembly -/docs/standard/assembly/ @IEvangelist -# Asynchronous Programming Patterns -/docs/standard/asynchronous-programming-patterns/ @BillWagner -# Attributes -/docs/standard/attributes/ @gewarren -# Character encoding -/docs/standard/base-types/character-encoding/ @gewarren -/docs/standard/base-types/character-encoding-introduction/ @gewarren -# Base types -/docs/standard/base-types/ @adegeo -# Collections -/docs/standard/collections/ @IEvangelist -# System.CommandLine -/docs/standard/commandline/ @tdykstra -# Data -/docs/standard/data/ @gewarren -# Data - SQLite -/docs/standard/data/sqlite/ @bricelam -# Datetime -/docs/standard/datetime/ @adegeo -# Design guidelines -/docs/standard/design-guidelines/ @BillWagner -# Events -/docs/standard/events/ @IEvangelist -# Exceptions -/docs/standard/exceptions/ @gewarren -# GC -/docs/standard/garbage-collection/ @gewarren -# Generics -/docs/standard/generics/ @adegeo -# Globalization -/docs/standard/globalization-localization/ @dotnet/docs -# I/O -/docs/standard/io/ @adegeo -# Library guidance -/docs/standard/library-guidance/ @jamesnk @IEvangelist -# LINQ -/docs/standard/linq/ @dotnet/docs -# Memory and spans -/docs/standard/memory-and-spans/ @gewarren -# Native Interop -/docs/standard/native-interop/ @jkoritzinsky -# Parallel programming -/docs/standard/parallel-programming/ @IEvangelist -# Security -/docs/standard/security/ @IEvangelist -# Serialization -/docs/standard/serialization/ @gewarren -# Threading -/docs/standard/threading/ @BillWagner -# What's New -/docs/standard/whats-new/ @dotnet/docs - -################## E-books ################## -# Blazor: -/docs/architecture/blazor-for-web-forms-developers/ @danroth27 @IEvangelist -# gRPC: -/docs/architecture/grpc-for-wcf-developers/ @IEvangelist -# Desktop: -/docs/architecture/modernize-desktop/ @OliaG @IEvangelist - -################# Samples folder ############## - -## These have been pulled directly from the samples repo: - -/samples/snippets/csharp/ @BillWagner @IEvangelist -# Visual Basic snippets: -/samples/snippets/visualbasic/ @BillWagner -# F# Snippets: -/samples/snippets/fsharp/ @dotnet/docs - -# WPF folders: -/samples/snippets/xaml/wpf/ @adegeo -/samples/snippets/csharp/wpf/ @adegeo -/samples/snippets/visualbasic/wpf/ @adegeo -/samples/snippets/desktop-guide/wpf/ @adegeo - -/samples/snippets/winforms/ @adegeo +# This file is intentionally empty in the live branch (only), so the whole world doesn't get tagged for review on main -> live publish PRs. diff --git a/.github/ISSUE_TEMPLATE/breaking-change.yml b/.github/ISSUE_TEMPLATE/breaking-change.yml index ff6780b22d197..818ad3b2958f0 100644 --- a/.github/ISSUE_TEMPLATE/breaking-change.yml +++ b/.github/ISSUE_TEMPLATE/breaking-change.yml @@ -1,7 +1,10 @@ name: ".NET breaking change" description: Report a change in .NET that breaks something that worked in a previous version. Intended mostly for product-team use. title: "[Breaking change]: " -labels: breaking-change,Pri1,doc-idea +labels: + - breaking-change + - Pri1 + - doc-idea assignees: - gewarren body: @@ -18,12 +21,11 @@ body: label: Version description: What version of .NET introduced the breaking change? options: - - .NET 7 Preview 5 - - .NET 7 Preview 6 - - .NET 7 Preview 7 - - .NET 7 RC 1 - - .NET 7 RC 2 - - .NET 7 GA + - .NET 7 + - .NET 8 Preview 1 + - .NET 8 Preview 2 + - .NET 8 Preview 3 + - .NET 8 (other) - Other (please put exact version in description textbox) validations: required: true @@ -49,8 +51,6 @@ body: options: - label: "**Binary incompatible**: Existing binaries may encounter a breaking change in behavior, such as failure to load/execute or different run-time behavior." - label: "**Source incompatible**: Source code may encounter a breaking change in behavior when targeting the new runtime/component/SDK, such as compile errors or different run-time behavior." - validations: - required: true - type: textarea id: reason attributes: @@ -84,6 +84,7 @@ body: - Interop - JIT - LINQ + - .NET MAUI - Networking - SDK - Security diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5800d9808a455..34f413f31725b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,99 +1,111 @@ -# generated by dependadotnet -# https://github.com/dotnet/core/tree/master/samples/dependadotnet +# Generated by dependabot-bot. +# https://github.com/dotnet/docs-tools/tree/main/actions/dependabot-bot version: 2 updates: + - package-ecosystem: "github-actions" # Core GitHub Actions + directory: "/" + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 10 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/pipelines" #Pipes.csproj + directory: "/docs/azure/sdk/snippets/logging" #LoggingSampleApp.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/xunit-test" #xunit-test.csproj + directory: "/docs/azure/sdk/snippets/pagination" #pagination.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/new-in-6" #new-in-6.csproj + directory: "/docs/core/diagnostics/snippets/Microsoft.Diagnostics.NETCore.Client/csharp" #Microsoft.Diagnostics.NETCore.Client.Samples.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/framework/migration-guide/snippets/visual-basic" #FrameworkVersions.vbproj + directory: "/docs/core/docker/snippets/Worker" #DotNet.ContainerImage.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/framework/migration-guide/snippets/csharp" #FrameworkVersions.csproj + directory: "/docs/core/extensions/snippets/caching/distributed" #distributed-apis.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/standard/assembly/snippets/inspect-contents-using-metadataloadcontext" #AssemblySnippets.csproj + directory: "/docs/core/extensions/snippets/caching/memory-apis" #memory-apis.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/standard/io/snippets/pipelines" #ConsoleApp.csproj + directory: "/docs/core/extensions/snippets/caching/memory-worker" #memory-worker.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/safe-efficient-code/benchmark" #benchmark.csproj + directory: "/docs/core/extensions/snippets/configuration/app-lifetime" #app-lifetime.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/getting-started/console-webapiclient" #webapiclient.csproj + directory: "/docs/core/extensions/snippets/configuration/console-basic" #console-basic.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/roslyn-sdk/SemanticQuickStart" #SemanticQuickStart.csproj + directory: "/docs/core/extensions/snippets/configuration/console-custom-logging" #console-custom-logging.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/generatingahash" #generatingahash.vbproj + directory: "/docs/core/extensions/snippets/configuration/console-di-disposable" #console-di-disposable.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/worker-service-options" #worker-service-options.csproj + directory: "/docs/core/extensions/snippets/configuration/console-di-ienumerable" #console-di-ienumerable.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/custom-provider" #custom-provider.csproj + directory: "/docs/core/extensions/snippets/configuration/console-di" #console-di.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/app-lifetime" #app-lifetime.csproj + directory: "/docs/core/extensions/snippets/configuration/console-env" #console-env.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/worker-service" #worker-service.csproj + directory: "/docs/core/extensions/snippets/configuration/console-host" #console-host.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-di-disposable" #console-di-disposable.csproj + directory: "/docs/core/extensions/snippets/configuration/console-indexer" #console-indexer.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/configuration/console-ini" #console-ini.csproj schedule: interval: "weekly" day: "wednesday" @@ -105,13 +117,13 @@ updates: day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-di-ienumerable" #console-di-ienumerable.csproj + directory: "/docs/core/extensions/snippets/configuration/console-memory" #console-memory.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-di" #console-di.csproj + directory: "/docs/core/extensions/snippets/configuration/console-raw" #console-raw.csproj schedule: interval: "weekly" day: "wednesday" @@ -123,61 +135,109 @@ updates: day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-env" #console-env.csproj + directory: "/docs/core/extensions/snippets/configuration/console" #console.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-ini" #console-ini.csproj + directory: "/docs/core/extensions/snippets/configuration/custom-provider" #custom-provider.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-memory" #console-memory.csproj + directory: "/docs/core/extensions/snippets/configuration/dependency-injection" #dependency-injection.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-custom-logging" #console-custom-logging.csproj + directory: "/docs/core/extensions/snippets/configuration/di-anti-patterns" #di-anti-patterns.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console-host" #console-host.csproj + directory: "/docs/core/extensions/snippets/configuration/options-action" #options-action.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/dependency-injection" #dependency-injection.csproj + directory: "/docs/core/extensions/snippets/configuration/options-configparam" #options-configparam.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/console" #console.csproj + directory: "/docs/core/extensions/snippets/configuration/options-noparams" #options-noparams.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/configuration/di-anti-patterns" #di-anti-patterns.csproj + directory: "/docs/core/extensions/snippets/configuration/options-object" #options-object.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/logging/console-formatter-simple" #console-formatter-simple.csproj + directory: "/docs/core/extensions/snippets/configuration/options-postconfig" #options-postconfig.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/extensions/snippets/logging/console-formatter-json" #console-formatter-json.csproj + directory: "/docs/core/extensions/snippets/configuration/worker-scope" #worker-scope.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/configuration/worker-service" #worker-service.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/fileglobbing/example" #example.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/http/basic" #basic.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/http/generated" #generated.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/http/named" #named.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/http/typed" #typed.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/localization/example" #example.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/logging/console-formatter-custom-with-config" #console-formatter-custom-with-config.csproj schedule: interval: "weekly" day: "wednesday" @@ -188,12 +248,198 @@ updates: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/logging/console-formatter-json" #console-formatter-json.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/logging/console-formatter-simple" #console-formatter-simple.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 - package-ecosystem: "nuget" directory: "/docs/core/extensions/snippets/logging/console-formatter-systemd" #console-formatter-systemd.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/logging/logger-message-generator" #logger-message-generator.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/logging/worker-service-options" #worker-service-options.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/primitives/change" #tokens.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/primitives/string" #tokenizers.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/ratelimit/http" #http.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/workers/background-service" #App.WorkerService.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/workers/cloud-service" #App.CloudService.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/workers/queue-service" #App.QueueService.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/workers/scoped-service" #App.ScopedService.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/workers/signal-completion-service/App.SignalCompletionService" #App.SignalCompletionService.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/workers/timer-service" #App.TimerHostedService.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/extensions/snippets/workers/windows-service" #App.WindowsService.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/porting/snippets/upgrade-assistant-wpf-framework/csharp/StarVoteControl" #StarVoteControl.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/porting/snippets/upgrade-assistant-wpf-framework/csharp/WebSiteRatings" #WebSiteRatings.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/testing/snippets/order-unit-tests/csharp/MSTest.Project" #MSTest.Project.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/testing/snippets/order-unit-tests/csharp/NUnit.TestProject" #NUnit.Project.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/testing/snippets/order-unit-tests/csharp/XUnit.TestProject" #XUnit.Project.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/testing/snippets/unit-testing-using-mstest/csharp/PrimeService.Tests" #PrimeService.Tests.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/tutorials/snippets/library-with-visual-studio-6-0/csharp/StringLibraryTest" #StringLibraryTest.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/tutorials/snippets/library-with-visual-studio/csharp/StringLibraryTest" #StringLibraryTest.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/core/tutorials/snippets/library-with-visual-studio/vb/StringLibraryTest" #StringLibraryTest.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/language-reference/compiler-messages/snippets/null-warnings" #null-warnings.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/language-reference/keywords/snippets" #keywords.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/language-reference/operators/snippets/shared" #operators.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/roslyn-sdk/snippets/source-generators/SourceGenerator" #SourceGenerator.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/roslyn-sdk/tutorials/snippets/how-to-write-csharp-analyzer-code-fix/MakeConst/MakeConst.CodeFixes" #MakeConst.CodeFixes.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/roslyn-sdk/tutorials/snippets/how-to-write-csharp-analyzer-code-fix/MakeConst/MakeConst.Test" #MakeConst.Test.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/roslyn-sdk/tutorials/snippets/how-to-write-csharp-analyzer-code-fix/MakeConst/MakeConst.Vsix" #MakeConst.Vsix.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/csharp/roslyn-sdk/tutorials/snippets/how-to-write-csharp-analyzer-code-fix/MakeConst/MakeConst" #MakeConst.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 - package-ecosystem: "nuget" directory: "/docs/csharp/tutorials/snippets/generate-consume-asynchronous-streams/finished" #IssuePRreport.csproj schedule: @@ -207,13 +453,43 @@ updates: day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/csharp/language-reference/operators/snippets/shared" #operators.csproj + directory: "/docs/devops/snippets/create-dotnet-github-action/DotNet.GitHubAction" #DotNet.GitHubAction.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/machine-learning/tutorials/snippets/movie-recommendation/csharp" #MovieRecommendation.csproj + directory: "/docs/framework/migration-guide/snippets/csharp" #FrameworkVersions.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/framework/migration-guide/snippets/visual-basic" #FrameworkVersions.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/all-rules" #all-rules.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/fundamentals/code-analysis/quality-rules/snippets/vb/all-rules" #all-rules.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/machine-learning/tutorials/snippets/github-issue-classification/csharp" #GitHubIssueClassification.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/machine-learning/tutorials/snippets/image-classification/csharp" #TransferLearningTF.csproj schedule: interval: "weekly" day: "wednesday" @@ -224,6 +500,18 @@ updates: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/machine-learning/tutorials/snippets/movie-recommendation/csharp" #MovieRecommendation.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/docs/machine-learning/tutorials/snippets/phone-calls-anomaly-detection/csharp" #PhoneCallsAnomalyDetection.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 - package-ecosystem: "nuget" directory: "/docs/machine-learning/tutorials/snippets/predict-prices/csharp" #TaxiFarePrediction.csproj schedule: @@ -231,7 +519,7 @@ updates: day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/machine-learning/tutorials/snippets/image-classification/csharp" #TransferLearningTF.csproj + directory: "/docs/machine-learning/tutorials/snippets/sales-anomaly-detection/csharp" #ProductSalesAnomalyDetection.csproj schedule: interval: "weekly" day: "wednesday" @@ -243,577 +531,631 @@ updates: day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/machine-learning/tutorials/snippets/github-issue-classification/csharp" #GitHubIssueClassification.csproj + directory: "/docs/machine-learning/tutorials/snippets/text-classification-tf/csharp" #TextClassificationTF.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/machine-learning/tutorials/snippets/text-classification-tf/csharp" #TextClassificationTF.csproj + directory: "/docs/standard/assembly/snippets/identify/csharp" #AssemblySnippets.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/machine-learning/tutorials/snippets/sales-anomaly-detection/csharp" #ProductSalesAnomalyDetection.csproj + directory: "/docs/standard/assembly/snippets/identify/visual-basic" #AssemblySnippets.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/xml/VS_Snippets_Data/edm_example_model/edm_example_model" #edm_example_model.csproj + directory: "/docs/standard/assembly/snippets/inspect-contents-using-metadataloadcontext" #AssemblySnippets.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/BatchingSample" #BatchingSample.csproj + directory: "/docs/standard/commandline/snippets/customize-help/csharp" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/AggregateFunctionSample" #AggregateFunctionSample.csproj + directory: "/docs/standard/commandline/snippets/define-commands/csharp" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/SqliteProviderSample" #SqliteProviderSample.csproj + directory: "/docs/standard/commandline/snippets/dependency-injection/csharp" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/InteropSample" #InteropSample.csproj + directory: "/docs/standard/commandline/snippets/get-started-tutorial/csharp/Stage1" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/InMemorySample" #InMemorySample.csproj + directory: "/docs/standard/commandline/snippets/get-started-tutorial/csharp/Stage2" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/DateAndTimeSample" #DateAndTimeSample.csproj + directory: "/docs/standard/commandline/snippets/get-started-tutorial/csharp/Stage3" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/ScalarFunctionSample" #ScalarFunctionSample.csproj + directory: "/docs/standard/commandline/snippets/handle-termination/csharp" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/BulkInsertSample" #BulkInsertSample.csproj + directory: "/docs/standard/commandline/snippets/model-binding/csharp" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/DeferredTransactionSample" #DeferredTransactionSample.csproj + directory: "/docs/standard/commandline/snippets/tab-completion/csharp" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/RegularExpressionSample" #RegularExpressionSample.csproj + directory: "/docs/standard/commandline/snippets/use-middleware/csharp" #scl.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/AsyncSample" #AsyncSample.csproj + directory: "/docs/standard/io/snippets/pipelines_1" #Pipes.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/EncryptionSample" #EncryptionSample.csproj + directory: "/docs/standard/io/snippets/pipelines_2" #ConsoleApp.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/DirtyReadSample" #DirtyReadSample.csproj + directory: "/docs/standard/security/snippets/replacing-a-principal-object/csharp" #SetCurrentPrincipal.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/CollationSample" #CollationSample.csproj + directory: "/docs/standard/security/snippets/replacing-a-principal-object/vb" #SetCurrentPrincipal.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/DapperSample" #DapperSample.csproj + directory: "/samples/snippets/core/testing/unit-testing-best-practices/csharp/after" #unit-testing-best-practices-after.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/ExtensionsSample" #ExtensionsSample.csproj + directory: "/samples/snippets/core/testing/unit-testing-best-practices/csharp/before" #unit-testing-best-practices-before.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/SystemLibrarySample" #SystemLibrarySample.csproj + directory: "/samples/snippets/core/testing/unit-testing-using-dotnet-test/csharp/PrimeService.Tests" #PrimeService.Tests.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/StreamingSample" #StreamingSample.csproj + directory: "/samples/snippets/core/testing/unit-testing-using-nunit/csharp/PrimeService.Tests" #PrimeService.Tests.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/ResultMetadataSample" #ResultMetadataSample.csproj + directory: "/samples/snippets/core/testing/unit-testing-vb-dotnet-test/vb/PrimeService.Tests" #PrimeService.Tests.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/BackupSample" #BackupSample.csproj + directory: "/samples/snippets/core/testing/unit-testing-vb-mstest/vb/PrimeService.Tests" #PrimeService.Tests.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/standard/data/sqlite/HelloWorldSample" #HelloWorldSample.csproj + directory: "/samples/snippets/core/testing/unit-testing-vb-nunit/vb/PrimeService.Tests" #PrimeService.Tests.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/cpp/VS_Snippets_Misc/astoria reflection provider/customdataserviceclient" #customdataserviceclient.csproj + directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/FrenchPlugin" #FrenchPlugin.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToEncryptXMLElementX509/cs" #encryptxml.csproj + directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/JsonPlugin" #JsonPlugin.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToDecryptXMLElementAsymmetric/cs" #decryptxml.csproj + directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/OldJsonPlugin" #OldJsonPlugin.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToEncryptXMLElementSymmetric/cs" #encryptxml.csproj + directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/UVPlugin" #UVPlugin.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToEncryptXMLElementAsymmetric/cs" #encryptxml.csproj + directory: "/samples/snippets/core/tutorials/testing-with-cli/csharp/test/NewTypesTests" #NewTypesTests.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/generatingahash/cs" #generatingahash.csproj + directory: "/samples/snippets/csharp/concepts/linq/LinqSamples.Test" #LinqSamples.Test.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToVerifyXMLDocumentRSA/cs" #verifyxml.csproj + directory: "/samples/snippets/csharp/getting_started/ClassLibraryProjects/StringLibraryTest" #StringLibraryTest.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToDecryptXMLElementX509/cs" #decryptxml.csproj + directory: "/samples/snippets/csharp/getting-started/console-webapiclient" #webapiclient.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToSignXMLDocumentRSA/cs" #signxml.csproj + directory: "/samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees" #dynamic-linq-expression-trees.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_VBCSharp/CsProgGuideTypes/CS" #CS.csproj + directory: "/samples/snippets/csharp/roslyn-sdk/SemanticQuickStart" #SemanticQuickStart.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/DP L2E Examples/CS" #l2e_examplescs.csproj + directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxQuickStart/HelloSyntaxTree" #HelloSyntaxTree.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/l2s_querycache/cs" #cs.csproj + directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxQuickStart/SyntaxWalker" #SyntaxWalker.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e canonicalandstorefunctions/cs" #canonicalandstorefunctions.csproj + directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxTransformationQuickStart/ConstructionCS" #ConstructionCS.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/dp conceptualmodelfunctions/cs" #conceptualmodelfunctions.csproj + directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxTransformationQuickStart/TransformationCS" #TransformationCS.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/dp entityservices concepts/cs" #entityservices.csproj + directory: "/samples/snippets/csharp/safe-efficient-code/benchmark" #benchmark.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/DP ObjectServices Concepts/CS" #objectservicesconceptscs.csproj + directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/finished/SimpleFeedReader.Tests" #SimpleFeedReader.Tests.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e methods on objectcontext/cs" #methodsonobjectcontext.csproj + directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/finished/SimpleFeedReader" #SimpleFeedReader.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/DP L2E Conceptual Examples/CS" #l2e_conceptualexamplescs.csproj + directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/start/SimpleFeedReader.Tests" #SimpleFeedReader.Tests.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/DP L2E Materialization Example/CS" #L2EMaterializationCS.csproj + directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/start/SimpleFeedReader" #SimpleFeedReader.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e arraysandlistsinqueries/cs" #l2e_arraysandlistsinqueriescs.csproj + directory: "/samples/snippets/csharp/VS_Snippets_ADO.NET/DataWorks SqlClient.DataAdapterUpdate/CS" #SqlAdapter.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e maptodbfunction/cs" #CustomStoreFunction.csproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/CryptoWalkThru/cs" #cryptowalkthru.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxTransformationQuickStart/TransformationCS" #TransformationCS.csproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/DPAPI-HowTO/cs" #sample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxTransformationQuickStart/ConstructionCS" #ConstructionCS.csproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/generatingahash/cs" #generatingahash.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxQuickStart/SyntaxWalker" #SyntaxWalker.csproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToDecryptXMLElementAsymmetric/cs" #decryptxml.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/roslyn-sdk/SyntaxQuickStart/HelloSyntaxTree" #HelloSyntaxTree.csproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToDecryptXMLElementX509/cs" #decryptxml.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/getting_started/ClassLibraryProjects/StringLibraryTest" #StringLibraryTest.csproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToEncryptXMLElementAsymmetric/cs" #encryptxml.csproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToEncryptXMLElementSymmetric/cs" #encryptxml.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/XAMLOvwSupport/VisualBasic" #WpfApplication7.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToEncryptXMLElementX509/cs" #encryptxml.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/XLinqExample/visualbasic" #xlinqexample.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToSignXMLDocumentRSA/cs" #signxml.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WindowClosingSnippets/visualbasic" #windowclosingsnippets.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_CLR/HowToVerifyXMLDocumentRSA/cs" #verifyxml.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WpfLayoutHostingWfWithXaml/VisualBasic" #WpfLayoutHostingWfWithXaml.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/dp conceptualmodelfunctions/cs" #conceptualmodelfunctions.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WinformWPFInk/VisualBasic" #WinformsWPFInk.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/dp entityservices concepts/cs" #entityservices.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/vsmcustomcontrol/visualbasic" #vsmcustomcontrolvb2.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e arraysandlistsinqueries/cs" #l2e_arraysandlistsinqueriescs.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/UIAAutomationID_snip/VisualBasic" #UIAAutomationID_snip.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e canonicalandstorefunctions/cs" #canonicalandstorefunctions.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WindowOwnerOwnedWindowsSnippets/visualbasic" #windowownerownedwindowssnippets.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/DP L2E Conceptual Examples/CS" #l2e_conceptualexamplescs.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_ADO.NET/DP LINQ to DataSet Examples/VB" #LINQtoDataSetExamplesVB.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/DP L2E Examples/CS" #l2e_examplescs.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_ADO.NET/DP Custom CopyToDataTable Examples/VB" #CustomCopyToDataTableVB.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e maptodbfunction/cs" #CustomStoreFunction.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToEncryptXMLElementX509/vb" #encryptxml.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/DP L2E Materialization Example/CS" #L2EMaterializationCS.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToDecryptXMLElementAsymmetric/vb" #decryptxml.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/dp l2e methods on objectcontext/cs" #methodsonobjectcontext.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToEncryptXMLElementSymmetric/vb" #encryptxml.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/DP ObjectServices Concepts/CS" #objectservicesconceptscs.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToEncryptXMLElementAsymmetric/vb" #encryptxml.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_Data/l2s_querycache/cs" #cs.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToVerifyXMLDocumentRSA/vb" #verifyxml.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_VBCSharp/CsProgGuideTypes/CS" #CS.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToDecryptXMLElementX509/vb" #decryptxml.vbproj + directory: "/samples/snippets/csharp/VS_Snippets_VBCSharp/vbdynamicwalkthroughironpython/cs" #ironpythonsample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToSignXMLDocumentRSA/vb" #signxml.vbproj + directory: "/samples/snippets/csharp/xunit-test" #xunit-test.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/fundamentals/code-analysis/quality-rules/snippets/vb/all-rules" #all-rules.vbproj + directory: "/samples/snippets/standard/data/sqlite/AggregateFunctionSample" #AggregateFunctionSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/all-rules" #all-rules.csproj + directory: "/samples/snippets/standard/data/sqlite/AsyncSample" #AsyncSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/tutorials/snippets/library-with-visual-studio/vb/StringLibraryTest" #StringLibraryTest.vbproj + directory: "/samples/snippets/standard/data/sqlite/BackupSample" #BackupSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/docs/core/tutorials/snippets/library-with-visual-studio/csharp/StringLibraryTest" #StringLibraryTest.csproj + directory: "/samples/snippets/standard/data/sqlite/BatchingSample" #BatchingSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-using-mstest/csharp/PrimeService.Tests" #PrimeService.Tests.csproj + directory: "/samples/snippets/standard/data/sqlite/BulkInsertSample" #BulkInsertSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-using-dotnet-test/csharp/PrimeService.Tests" #PrimeService.Tests.csproj + directory: "/samples/snippets/standard/data/sqlite/CollationSample" #CollationSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-best-practices/csharp/before" #unit-testing-best-practices-before.csproj + directory: "/samples/snippets/standard/data/sqlite/DapperSample" #DapperSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-best-practices/csharp/after" #unit-testing-best-practices-after.csproj + directory: "/samples/snippets/standard/data/sqlite/DateAndTimeSample" #DateAndTimeSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-using-nunit/csharp/PrimeService.Tests" #PrimeService.Tests.csproj + directory: "/samples/snippets/standard/data/sqlite/DeferredTransactionSample" #DeferredTransactionSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-vb-nunit/vb/PrimeService.Tests" #PrimeService.Tests.vbproj + directory: "/samples/snippets/standard/data/sqlite/DirtyReadSample" #DirtyReadSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-vb-dotnet-test/vb/PrimeService.Tests" #PrimeService.Tests.vbproj + directory: "/samples/snippets/standard/data/sqlite/EncryptionSample" #EncryptionSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/testing/unit-testing-vb-mstest/vb/PrimeService.Tests" #PrimeService.Tests.vbproj + directory: "/samples/snippets/standard/data/sqlite/ExtensionsSample" #ExtensionsSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/XcopyablePlugin" #XcopyablePlugin.csproj + directory: "/samples/snippets/standard/data/sqlite/HelloWorldSample" #HelloWorldSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/FrenchPlugin" #FrenchPlugin.csproj + directory: "/samples/snippets/standard/data/sqlite/InMemorySample" #InMemorySample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/JsonPlugin" #JsonPlugin.csproj + directory: "/samples/snippets/standard/data/sqlite/InteropSample" #InteropSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/OldJsonPlugin" #OldJsonPlugin.csproj + directory: "/samples/snippets/standard/data/sqlite/RegularExpressionSample" #RegularExpressionSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/tutorials/creating-app-with-plugin-support/csharp/UVPlugin" #UVPlugin.csproj + directory: "/samples/snippets/standard/data/sqlite/ResultMetadataSample" #ResultMetadataSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/finished/SimpleFeedReader.Tests" #SimpleFeedReader.Tests.csproj + directory: "/samples/snippets/standard/data/sqlite/SavepointSample" #SavepointSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/finished/SimpleFeedReader" #SimpleFeedReader.csproj + directory: "/samples/snippets/standard/data/sqlite/ScalarFunctionSample" #ScalarFunctionSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/start/SimpleFeedReader.Tests" #SimpleFeedReader.Tests.csproj + directory: "/samples/snippets/standard/data/sqlite/SqliteProviderSample" #SqliteProviderSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/csharp/tutorials/nullable-reference-migration/start/SimpleFeedReader" #SimpleFeedReader.csproj + directory: "/samples/snippets/standard/data/sqlite/StreamingSample" #StreamingSample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Misc/tpldataflow_cancellationwinforms/vb/cancellationwinforms" #cancellationwinforms.vbproj + directory: "/samples/snippets/standard/data/sqlite/SystemLibrarySample" #SystemLibrarySample.csproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WPFWithWFAndDatabinding/VisualBasic/WPFWithWFAndDatabinding" #WPFWithWFAndDatabinding.vbproj + directory: "/samples/snippets/visualbasic/programming-guide/dynamic-linq-expression-trees" #dynamic-linq-expression-trees.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WPFAssemblyResourcesSnippets/VisualBasic/ResourcesSample" #ResourcesSample.vbproj + directory: "/samples/snippets/visualbasic/VS_Snippets_ADO.NET/DP Custom CopyToDataTable Examples/VB" #CustomCopyToDataTableVB.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WPFAssemblyResourcesSnippets/VisualBasic/ResourceLibrary" #ResourceLibrary.vbproj + directory: "/samples/snippets/visualbasic/VS_Snippets_ADO.NET/DP LINQ to DataSet Examples/VB" #LINQtoDataSetExamplesVB.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WpfHostingWindowsFormsControl/VisualBasic/MyControls" #MyControls.vbproj + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/generatingahash" #generatingahash.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/WpfHostingWindowsFormsControl/VisualBasic/WpfHost" #WpfHost.vbproj + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToDecryptXMLElementAsymmetric/vb" #decryptxml.vbproj schedule: interval: "weekly" day: "wednesday" open-pull-requests-limit: 5 - package-ecosystem: "nuget" - directory: "/samples/snippets/core/tutorials/testing-with-cli/csharp/test/NewTypesTests" #NewTypesTests.csproj + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToDecryptXMLElementX509/vb" #decryptxml.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToEncryptXMLElementAsymmetric/vb" #encryptxml.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToEncryptXMLElementSymmetric/vb" #encryptxml.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToEncryptXMLElementX509/vb" #encryptxml.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToSignXMLDocumentRSA/vb" #signxml.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_CLR/HowToVerifyXMLDocumentRSA/vb" #verifyxml.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_Misc/tpldataflow_cancellationwinforms/vb/cancellationwinforms" #cancellationwinforms.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_VBCSharp/vbdynamicwalkthroughironpython/vb" #ironpythonsample.vbproj + schedule: + interval: "weekly" + day: "wednesday" + open-pull-requests-limit: 5 + - package-ecosystem: "nuget" + directory: "/samples/snippets/visualbasic/VS_Snippets_Wpf/UIAAutomationID_snip/VisualBasic" #UIAAutomationID_snip.vbproj schedule: interval: "weekly" day: "wednesday" diff --git a/.github/fabricbot.json b/.github/fabricbot.json new file mode 100644 index 0000000000000..972fbab3c1369 --- /dev/null +++ b/.github/fabricbot.json @@ -0,0 +1,570 @@ +{ + "version": "1.0", + "tasks": [ + { + "taskType": "trigger", + "capabilityId": "InPrLabel", + "subCapability": "InPrLabel", + "version": "1.0", + "config": { + "label_inPr": "in-pr", + "taskName": "Add in-pr label to issues" + } + }, + { + "taskType": "scheduled", + "capabilityId": "ScheduledPR", + "subCapability": "ScheduledPR", + "version": "1.0", + "config": { + "head": "main", + "base": "live", + "body": "Please don't squash-merge this PR.", + "title": "Merge main into live", + "frequency": [ + { + "weekDay": 1, + "hours": [ + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 2, + "hours": [ + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 3, + "hours": [ + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 4, + "hours": [ + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 5, + "hours": [ + 22 + ], + "timezoneOffset": -7 + } + ], + "taskName": "Push to live branch (publish)" + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "PullRequestResponder", + "version": "1.0", + "config": { + "taskName": "Label community PRs", + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "community-contribution" + } + } + ], + "eventType": "pull_request", + "eventNames": [ + "pull_request", + "issues", + "project_card" + ], + "conditions": { + "operator": "and", + "operands": [ + { + "name": "isAction", + "parameters": { + "action": "opened" + } + }, + { + "operator": "and", + "operands": [ + { + "operator": "not", + "operands": [ + { + "name": "activitySenderHasPermissions", + "parameters": { + "permissions": "admin" + } + } + ] + }, + { + "operator": "not", + "operands": [ + { + "name": "activitySenderHasPermissions", + "parameters": { + "permissions": "maintain" + } + } + ] + }, + { + "operator": "not", + "operands": [ + { + "name": "activitySenderHasPermissions", + "parameters": { + "permissions": "write" + } + } + ] + }, + { + "operator": "not", + "operands": [ + { + "name": "isActivitySender", + "parameters": { + "user": "github-actions[bot]" + } + } + ] + }, + { + "operator": "not", + "operands": [ + { + "name": "isActivitySender", + "parameters": { + "user": "github-actions" + } + } + ] + }, + { + "operator": "not", + "operands": [ + { + "name": "isActivitySender", + "parameters": { + "user": "azure-sdk" + } + } + ] + }, + { + "operator": "not", + "operands": [ + { + "name": "isActivitySender", + "parameters": { + "user": "dependabot" + } + } + ] + } + ] + } + ] + } + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "PullRequestResponder", + "version": "1.0", + "config": { + "conditions": { + "operator": "and", + "operands": [ + { + "name": "prMatchesPattern", + "parameters": { + "matchRegex": "docs\\/[A-z\\/\\d\\-]*\\/[A-z\\-\\d]*\\.md" + } + } + ] + }, + "eventType": "pull_request", + "eventNames": [ + "pull_request", + "issues", + "project_card" + ], + "taskName": "Insert preview link", + "actions": [ + { + "name": "addReply", + "parameters": { + "comment": "[Internal preview link](https://review.docs.microsoft.com/dotnet/fundamentals/?branch=pr-en-us-${number})" + } + } + ] + }, + "disabled": true + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "PullRequestResponder", + "version": "1.0", + "config": { + "conditions": { + "operator": "and", + "operands": [ + { + "name": "isActivitySender", + "parameters": { + "user": "azure-sdk" + } + } + ] + }, + "eventType": "pull_request", + "eventNames": [ + "pull_request", + "issues", + "project_card" + ], + "taskName": "Label PRs from the Azure SDK bot", + "actions": [ + { + "name": "approvePullRequest", + "parameters": { + "comment": "Approved, we'll :shipit: when all status checks pass." + } + }, + { + "name": "addLabel", + "parameters": { + "label": "azure-sdk-shipit" + } + } + ] + } + }, + { + "taskType": "trigger", + "capabilityId": "AutoMerge", + "subCapability": "AutoMerge", + "version": "1.0", + "config": { + "taskName": "Auto-merge Azure SDK PRs with green builds", + "label": "azure-sdk-shipit", + "allowAutoMergeInstructionsWithoutLabel": false, + "mergeType": "squash", + "requireAllStatuses": false, + "requireSpecificCheckRuns": true, + "requireSpecificCheckRunsList": [ + "lint", + "Look for build warnings", + "MSDocs build verifier" + ] + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "IssuesOnlyResponder", + "version": "1.0", + "config": { + "conditions": { + "operator": "and", + "operands": [ + { + "name": "titleContains", + "parameters": { + "titlePattern": "(T|t)ypo", + "isRegex": true + } + } + ] + }, + "eventType": "issue", + "eventNames": [ + "issues", + "project_card" + ], + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "doc-bug" + } + }, + { + "name": "addLabel", + "parameters": { + "label": "help wanted" + } + }, + { + "name": "addLabel", + "parameters": { + "label": "good first issue" + } + } + ], + "taskName": "Label typo issues as help wanted" + } + }, + { + "taskType": "trigger", + "capabilityId": "LabelSync", + "subCapability": "LabelSync", + "version": "1.0", + "config": { + "taskName": "Synchronize OKR and release labels between issues/PRs", + "labelPatterns": [ + { + "pattern": "okr-" + }, + { + "pattern": ":checkered_flag: Release" + } + ] + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "IssuesOnlyResponder", + "version": "1.0", + "config": { + "conditions": { + "operator": "or", + "operands": [ + { + "name": "hasLabel", + "parameters": { + "label": "doc-bug" + } + }, + { + "name": "titleContains", + "parameters": { + "titlePattern": "freshness" + } + }, + { + "name": "titleContains", + "parameters": { + "titlePattern": "out( |-)of( |-)date", + "isRegex": true + } + } + ] + }, + "eventType": "issue", + "eventNames": [ + "issues", + "project_card" + ], + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "okr-health" + } + } + ], + "taskName": "Label issues with okr-health (event-based)" + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "PullRequestResponder", + "version": "1.0", + "config": { + "conditions": { + "operator": "or", + "operands": [ + { + "name": "titleContains", + "parameters": { + "titlePattern": "build warning" + } + }, + { + "name": "bodyContains", + "parameters": { + "bodyPattern": "build warning" + } + }, + { + "name": "titleContains", + "parameters": { + "titlePattern": "freshness" + } + } + ] + }, + "eventType": "pull_request", + "eventNames": [ + "pull_request", + "issues", + "project_card" + ], + "taskName": "Label PRs with okr-health", + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "okr-health" + } + } + ] + } + }, + { + "taskType": "scheduled", + "capabilityId": "ScheduledSearch", + "subCapability": "ScheduledSearch", + "version": "1.1", + "config": { + "frequency": [ + { + "weekDay": 0, + "hours": [ + 1, + 4, + 7, + 10, + 13, + 16, + 19, + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 1, + "hours": [ + 1, + 4, + 7, + 10, + 13, + 16, + 19, + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 2, + "hours": [ + 1, + 4, + 7, + 10, + 13, + 16, + 19, + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 3, + "hours": [ + 1, + 4, + 7, + 10, + 13, + 16, + 19, + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 4, + "hours": [ + 1, + 4, + 7, + 10, + 13, + 16, + 19, + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 5, + "hours": [ + 1, + 4, + 7, + 10, + 13, + 16, + 19, + 22 + ], + "timezoneOffset": -7 + }, + { + "weekDay": 6, + "hours": [ + 1, + 4, + 7, + 10, + 13, + 16, + 19, + 22 + ], + "timezoneOffset": -7 + } + ], + "searchTerms": [ + { + "name": "isIssue", + "parameters": {} + }, + { + "name": "isOpen", + "parameters": {} + }, + { + "name": "hasLabel", + "parameters": { + "label": "doc-bug" + } + }, + { + "name": "noLabel", + "parameters": { + "label": "okr-health" + } + } + ], + "taskName": "Label doc-bug issues with okr-health (scheduled search)", + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "okr-health" + } + } + ] + } + } + ], + "userGroups": [] +} \ No newline at end of file diff --git a/.github/no-response.yml b/.github/no-response.yml deleted file mode 100644 index 48b5f969edd4a..0000000000000 --- a/.github/no-response.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Configuration for probot-no-response - https://github.com/probot/no-response - -# Number of days of inactivity before an Issue is closed for lack of response -daysUntilClose: 14 -# Label requiring a response -responseRequiredLabel: needs-more-info -# Comment to post when closing an Issue for lack of response. Set to `false` to disable -closeComment: > - This issue has been automatically closed due to no response from the original author. - Please feel free to reopen it if you have more information that can help us investigate the issue further. diff --git a/.github/workflows/bcnotify.yaml b/.github/workflows/bcnotify.yaml deleted file mode 100644 index dcd8a1ceb3616..0000000000000 --- a/.github/workflows/bcnotify.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: "bc-notification" -on: - issues: - types: [edited, labeled] - -jobs: - test: - runs-on: ubuntu-latest - if: github.event.label.name == 'breaking-change' - steps: - - uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 #@v2 - - uses: timheuer/issue-notifier@84b8e0081c0abce88ac2673f1f3ad8529a040586 #@v1.0.2 - env: - SENDGRID_API_KEY: ${{ secrets.SENDGRID_API }} - with: - fromMailAddress: '${{ secrets.BC_NOTIFY }}' - toMailAddress: '${{ secrets.BC_NOTIFY }}' - subject: 'BC:' - subjectPrefix: 'BC:' - labelsToMonitor: "breaking-change" diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 09c26589e269b..0ee6abf9c20c3 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -1,23 +1,24 @@ # This is a basic workflow to help you get started with Actions -name: 'Snippets 5000' +name: "Snippets 5000" # Controls when the action will run. Triggers the workflow on push or pull request # events on the main branch only. on: pull_request: - branches: [ main ] + branches: [main] types: [opened, synchronize, reopened] workflow_dispatch: inputs: reason: - description: 'The reason for running the workflow' + description: "The reason for running the workflow" required: true - default: 'Manual run' + default: "Manual run" env: - DOTNET_INSTALLER_CHANNEL: '7.0' - DOTNET_DO_INSTALL: 'true' # True to install preview versions, False to use the pre-installed (released) SDK - EnableNuGetPackageRestore: 'True' + DOTNET_INSTALLER_CHANNEL: "7.0" + DOTNET_DO_INSTALL: "true" # True to install preview versions, False to use the pre-installed (released) SDK + EnableNuGetPackageRestore: "True" + repo: "docs" # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -26,48 +27,48 @@ jobs: # The type of runner that the job will run on runs-on: windows-2022 permissions: - statuses: write + statuses: write # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 #@v2 + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - # Get the latest preview SDK (or sdk not installed by the runner) - - name: Setup .NET SDK - if: ${{ env.DOTNET_DO_INSTALL == 'true' }} - run: | - echo "Downloading dotnet-install.ps1" - Invoke-WebRequest https://raw.githubusercontent.com/dotnet/install-scripts/master/src/dotnet-install.ps1 -OutFile dotnet-install.ps1 - echo "Installing dotnet version ${{ env.DOTNET_INSTALLER_CHANNEL }}" - .\dotnet-install.ps1 -InstallDir "c:\program files\dotnet" -Channel "${{ env.DOTNET_INSTALLER_CHANNEL }}" -Quality preview + # Get the latest preview SDK (or sdk not installed by the runner) + - name: Setup .NET SDK + if: ${{ env.DOTNET_DO_INSTALL == 'true' }} + run: | + echo "Downloading dotnet-install.ps1" + Invoke-WebRequest https://raw.githubusercontent.com/dotnet/install-scripts/master/src/dotnet-install.ps1 -OutFile dotnet-install.ps1 + echo "Installing dotnet version ${{ env.DOTNET_INSTALLER_CHANNEL }}" + .\dotnet-install.ps1 -InstallDir "c:\program files\dotnet" -Channel "${{ env.DOTNET_INSTALLER_CHANNEL }}" -Quality preview - # Print dotnet info - - name: Display .NET info - run: | - dotnet --info + # Print dotnet info + - name: Display .NET info + run: | + dotnet --info - # Install locate projs global tool - - name: Install LocateProjects tool - run: | - dotnet tool install --global --add-source ./.github/workflows/dependencies/ DotnetDocsTools.LocateProjects - - # Run locate projs tool - - name: Locate projects for PR - env: - GitHubKey: ${{ secrets.GITHUB_TOKEN }} - LocateExts: ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml;.csproj;.fsproj;.vbproj;.vcxproj;.sln" - run: | - ./.github/workflows/dependencies/Get-MSBuildResults.ps1 "${{ github.workspace }}" -PullRequest ${{ github.event.number }} -RepoOwner ${{ github.repository_owner }} -RepoName ${{ github.event.repository.name }} - - # Update build output json file - - name: Upload build results - uses: actions/upload-artifact@3446296876d12d4e3a0f3145a3c87e67bf0a16b5 #@v1 - with: - name: build - path: ./output.json + # Install locate projs global tool + - name: Install LocateProjects tool + run: | + dotnet tool install --global --add-source ./.github/workflows/dependencies/ DotnetDocsTools.LocateProjects - # Return status based on json file - - name: Report status - run: | - ./.github/workflows/dependencies/Out-GithubActionStatus.ps1 + # Run locate projs tool + - name: Locate projects for PR + env: + GitHubKey: ${{ secrets.GITHUB_TOKEN }} + LocateExts: ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml;.csproj;.fsproj;.vbproj;.vcxproj;.sln" + run: | + ./.github/workflows/dependencies/Get-MSBuildResults.ps1 "${{ github.workspace }}" -PullRequest ${{ github.event.number }} -RepoOwner ${{ github.repository_owner }} -RepoName ${{ github.event.repository.name }} + + # Update build output json file + - name: Upload build results + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb + with: + name: build + path: ./output.json + + # Return status based on json file + - name: Report status + run: | + ./.github/workflows/dependencies/Out-GithubActionStatus.ps1 diff --git a/.github/workflows/dependabot-approve-and-automerge.yml b/.github/workflows/dependabot-approve-and-automerge.yml new file mode 100644 index 0000000000000..bb970f430fea1 --- /dev/null +++ b/.github/workflows/dependabot-approve-and-automerge.yml @@ -0,0 +1,27 @@ +name: dependabot auto-approve and auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' && github.repository_owner == 'dotnet' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@bfc19f43c126171ed783cdcf9a125055b7831d32 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Approve a PR + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/dependabot-bot.yml b/.github/workflows/dependabot-bot.yml new file mode 100644 index 0000000000000..1c82a69999365 --- /dev/null +++ b/.github/workflows/dependabot-bot.yml @@ -0,0 +1,51 @@ +# The name used in the GitHub UI for the workflow +name: "update dependabot.yml" +# When to run this action: +# - Scheduled to run at 5 AM every Monday. +# - Manually runnable from the GitHub UI with a reason +on: + schedule: + - cron: "0 5 * * 1" # 5 AM every Monday. + workflow_dispatch: + inputs: + reason: + description: "The reason for running the workflow" + required: true + default: "Manual run" +# Run on the latest version of Ubuntu +jobs: + dependabot-bot: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + # Checkout the repo into the workspace within the VM + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + # - name: Setup .NET + # uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 + # with: + # dotnet-version: 7.0.x + # dotnet-quality: 'preview' + # If triggered manually, print the reason why + - name: "Print manual run reason" + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo "Reason: ${{ github.event.inputs.reason }}" + # Run the .NET dependabot-bot tool + - name: dependabot-bot + id: dependabot-bot + uses: dotnet/docs-tools/actions/dependabot-bot@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + root-directory: "/github/workspace" + dependabot-yml-path: ".github/dependabot.yml" + - name: Create pull request + if: github.event_name == 'workflow_dispatch' || github.repository_owner == 'dotnet' + uses: peter-evans/create-pull-request@b4d51739f96fca8047ad065eccef63442d8e99f7 + with: + title: "Update dependabot.yml - automatically." + body: ".NET dependabot-bot automated PR. 🤖" + commit-message: ".NET dependabot-bot automated PR." diff --git a/.github/workflows/dependencies/Get-MSBuildResults.ps1 b/.github/workflows/dependencies/Get-MSBuildResults.ps1 index 0fb2f4d4e0c3b..9d7480ba1998c 100644 --- a/.github/workflows/dependencies/Get-MSBuildResults.ps1 +++ b/.github/workflows/dependencies/Get-MSBuildResults.ps1 @@ -10,7 +10,7 @@ The directory of the repository files on the local machine. .PARAMETER PullRequest - The pull requst to process. If 0 or not passed, processes the whole repo + The pull request to process. If 0 or not passed, processes the whole repo .PARAMETER RepoOwner The name of the repository owner. @@ -31,11 +31,24 @@ None .NOTES - Version: 1.5 + + Version: 1.8 + Author: adegeo@microsoft.com + Creation Date: 12/11/2020 + Update Date: 10/05/2022 + Purpose/Change: Add support for discovering and processing settings file for project errors (not found, too many, etc) + + Version: 1.7 + Author: adegeo@microsoft.com + Creation Date: 12/11/2020 + Update Date: 09/26/2022 + Purpose/Change: Trim build error lines to help remove duplicates. + + Version: 1.6 Author: adegeo@microsoft.com Creation Date: 12/11/2020 - Update Date: 02/17/2022 - Purpose/Change: Move to VS 2022. + Update Date: 03/10/2022 + Purpose/Change: Export proj/sln settings config to output.json file. #> [CmdletBinding()] @@ -61,7 +74,7 @@ Param( $Global:statusOutput = @() -Write-Host "Gathering solutions and projects..." +Write-Host "Gathering solutions and projects... (v1.8)" if ($PullRequest -ne 0) { Write-Host "Running `"LocateProjects `"$RepoRootDir`" --pullrequest $PullRequest --owner $RepoOwner --repo $RepoName`"" @@ -78,7 +91,7 @@ if ($LASTEXITCODE -ne 0) throw "Error on running LocateProjects" } -function New-Result($inputFile, $projectFile, $exitcode, $outputText) +function New-Result($inputFile, $projectFile, $exitcode, $outputText, $settingsJson) { $info = @{} @@ -86,6 +99,7 @@ function New-Result($inputFile, $projectFile, $exitcode, $outputText) $info.ProjectFile = $projectFile $info.ExitCode = $exitcode $info.Output = $outputText + $info.Settings = $settingsJson $object = New-Object -TypeName PSObject -Prop $info $Global:statusOutput += $object @@ -100,13 +114,13 @@ if (($RangeStart -ne 0) -and ($RangeEnd -ne 0)){ # Log working set items prior to filtering $workingSet | Write-Host -# Remove duplicated projects +# Remove duplicated projects and skip snippets files from being processed $projects = @() $workingSetTemp = @() foreach ($item in $workingSet) { $data = $item.Split('|') - if ($projects.Contains($data[2].Trim())) { + if ($projects.Contains($data[2].Trim()) -or $data[1].EndsWith("snippets.5000.json")) { continue } if ($data[2].Trim() -ne "") { @@ -134,6 +148,7 @@ foreach ($item in $workingSet) { if ([int]$data[0] -eq 0) { $projectFile = Resolve-Path "$RepoRootDir\$($data[2])" $configFile = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($projectFile), "snippets.5000.json") + $settings = $null # Create the default build command "dotnet build `"$projectFile`"" | Out-File ".\run.bat" @@ -149,6 +164,7 @@ foreach ($item in $workingSet) { # Create the visual studio build command "CALL `"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat`"`n" + + "nuget.exe restore `"$projectFile`"`n" + "msbuild.exe `"$projectFile`" -restore:True" ` | Out-File ".\run.bat" } @@ -171,38 +187,68 @@ foreach ($item in $workingSet) { Get-Content .\run.bat | Write-Host Write-Host - Invoke-Expression ".\run.bat" | Tee-Object -Variable "result" $thisExitCode = 0 + Invoke-Expression ".\run.bat" | Out-String | Tee-Object -Variable "result" + if ($LASTEXITCODE -ne 0) { $thisExitCode = 4 } - New-Result $data[1] $projectFile $thisExitCode $result + New-Result $data[1] $projectFile $thisExitCode $result $settings } # No project found - elseif ([int]$data[0] -eq 1) { - New-Result $data[1] "" 1 "😵 Project missing. A project (and optionally a solution file) must be in this directory or one of the parent directories to validate and build this code." + else + { + $settings = $null + $filePath = Resolve-Path "$RepoRootDir\$($data[1])" - $thisExitCode = 1 - } + # Hunt for snippets config file + do { - # Too many projects found - elseif ([int]$data[0] -eq 2) { - New-Result $data[1] $data[2] 2 "😕 Too many projects found. A single project or solution must exist in this directory or one of the parent directories." + $configFile = [System.IO.Path]::Combine($filePath, "snippets.5000.json") - $thisExitCode = 2 - } + if ([System.IO.File]::Exists($configFile) -eq $true) { + + $settings = $configFile | Get-ChildItem | Get-Content | ConvertFrom-Json + Write-Host "Loading settings for errors found by LocateProjects: $configFile" + break + } + + # go back one folder + $filePath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($filePath, "..\")) + } until ([System.Linq.Enumerable]::Count($filePath, [Func[Char, Boolean]] { param($x) $x -eq '\' }) -eq 1) + + if ($settings -eq $null) { + Write-Host "No settings file found for LocateProjects reported error" + } + + # Process each error + if ([int]$data[0] -eq 1) { + New-Result $data[1] "" 1 "ERROR: Project missing. A project (and optionally a solution file) must be in this directory or one of the parent directories to validate and build this code." $settings + + $thisExitCode = 1 + } + + # Too many projects found + elseif ([int]$data[0] -eq 2) { + New-Result $data[1] $data[2] 2 "ERROR: Too many projects found. A single project or solution must exist in this directory or one of the parent directories." $settings + + $thisExitCode = 2 + } + + # Solution found, but no project + elseif ([int]$data[0] -eq 3) { + New-Result $data[1] $data[2] 2 "ERROR: Solution found, but missing project. A project is required to compile this code." $settings + $thisExitCode = 3 + } - # Solution found, but no project - elseif ([int]$data[0] -eq 3) { - New-Result $data[1] $data[2] 2 "😲 Solution found, but missing project. A project is required to compile this code." - $thisExitCode = 3 } + } catch { - New-Result $data[1] $projectFile 1000 "ERROR: $($_.Exception)" + New-Result $data[1] $projectFile 1000 "ERROR: $($_.Exception)" $null $thisExitCode = 4 Write-Host $_.Exception.Message -Foreground "Red" Write-Host $_.ScriptStackTrace -Foreground "DarkGray" @@ -211,7 +257,7 @@ foreach ($item in $workingSet) { $counter++ } -$resultItems = $Global:statusOutput | Select-Object InputFile, ProjectFile, ExitCode, Output +$resultItems = $Global:statusOutput | Select-Object InputFile, ProjectFile, ExitCode, Output, Settings # Add our output type $typeResult = @" @@ -221,6 +267,7 @@ public class ResultItem public string InputFile; public int ExitCode; public string BuildOutput; + public object Settings; public MSBuildError[] Errors; public int ErrorCount; @@ -238,10 +285,11 @@ $transformedItems = $resultItems | ForEach-Object { New-Object ResultItem -Prope InputFile = $_.InputFile; ExitCode = $_.ExitCode; BuildOutput = $_.Output; + Settings = $_.Settings; Errors = @(); - ErrorCount = 0} - } - + ErrorCount = 0; + } } + # Transform the build output to break it down into MSBuild result entries foreach ($item in $transformedItems) { $list = @() @@ -257,11 +305,13 @@ foreach ($item in $transformedItems) { $list += New-Object -TypeName "ResultItem+MSBuildError" -Property @{ Line = $item.BuildOutput; Error = $item.BuildOutput } $item.ErrorCount = 1 } + + # Actual build error found else { $errorInfo = $item.BuildOutput -Split [System.Environment]::NewLine | Select-String ": (?:Solution file error|error) ([^:]*)" | ` Select-Object Line -ExpandProperty Matches | ` - Select-Object Line, Groups | ` + Select-Object -Property @{Name = 'Line'; Expression = {$_.Line.Trim()}}, Groups | ` Sort-Object Line | Get-Unique -AsString $item.ErrorCount = $errorInfo.Count foreach ($err in $errorInfo) { @@ -275,11 +325,12 @@ foreach ($item in $transformedItems) { } } + # Set build errors $item.Errors = $list } -$transformedItems | ConvertTo-Json -Depth 3 | Out-File 'output.json' +$transformedItems | ConvertTo-Json -Depth 4 | Out-File 'output.json' exit 0 @@ -287,7 +338,15 @@ exit 0 # Sample snippets.5000.json file <# { - "host": "visualstudio" + "host": "visualstudio", + "expectederrors": [ + { + "file": "samples/snippets/csharp/VS_Snippets_VBCSharp/csprogguideindexedproperties/cs/Program.cs", + "line": 5, + "column": 25, + "error": "CS0234" + } + ] } #> diff --git a/.github/workflows/dependencies/Out-GithubActionStatus.ps1 b/.github/workflows/dependencies/Out-GithubActionStatus.ps1 index c0d4e3cab5a5b..b116a856d6f77 100644 --- a/.github/workflows/dependencies/Out-GithubActionStatus.ps1 +++ b/.github/workflows/dependencies/Out-GithubActionStatus.ps1 @@ -13,10 +13,11 @@ None .NOTES - Version: 1.1 + Version: 1.2 Author: adegeo@microsoft.com Creation Date: 06/24/2020 - Purpose/Change: Change reporting items + Update Date: 03/10/2022 + Purpose/Change: Support ignoring known errors. #> [CmdletBinding()] @@ -25,29 +26,54 @@ Param( $json = Get-Content output.json | ConvertFrom-Json -$errors = $json | Where-Object ErrorCount -ne 0 | Select-Object InputFile -ExpandProperty Errors | Select-Object InputFile, Error, Line +$errors = $json | Where-Object ErrorCount -ne 0 | Select-Object InputFile, Settings -ExpandProperty Errors | Select-Object InputFile, Settings, Error, Line -if ($errors.Count -eq 0) { +# Exit if no error entries were found +$count = $errors.Count + +if ($count -eq 0) { Write-Host "All builds passed" exit 0 } -Write-Host "Total errors: $($errors.Count)" +Write-Host "Total errors: $count" foreach ($er in $errors) { + $skipError = $false + $lineColMatch = $er.Line | Select-String "(^.*)\((\d*),(\d*)\)" | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Groups $errorFile = $er.InputFile $errorLineNumber = 0 $errorColNumber = 0 if ($lineColMatch.Count -eq 4) { - $errorFile = $lineColMatch[1].Value.Replace("D:\a\docs\docs\", "").Replace("\", "/") + $errorFile = $lineColMatch[1].Value.Replace("D:\a\$($env:repo)\$($env:repo)\", "").Replace("\", "/") $errorLineNumber = $lineColMatch[2].Value $errorColNumber = $lineColMatch[3].Value } - Write-Host "::error file=$errorFile,line=$errorLineNumber,col=$errorColNumber::$($er.Line)" + # Check if there are any errors that should be skipped because they're known failures + foreach ($expectedError in $er.Settings.expectederrors) { + if (($expectedError.file -eq $errorFile) -and ($expectedError.error -eq $er.error)) { + Write-Host "Skipping error:`n- File: $errorFile`n- Error: $($er.error)" + $skipError = $true + break + } + } + + if ($skipError -eq $false) { + Write-Host "::error file=$errorFile,line=$errorLineNumber,col=$errorColNumber::$($er.Line)" + } + else { + $count -= 1 + } +} + +Write-Host "Errors after skips: $count" + +if ($count -eq 0) { + exit 0 } exit 1 diff --git a/.github/workflows/dependencies/dotnet-whatsnew.2.1.7.nupkg b/.github/workflows/dependencies/dotnet-whatsnew.2.1.7.nupkg deleted file mode 100644 index dd834837036ab..0000000000000 Binary files a/.github/workflows/dependencies/dotnet-whatsnew.2.1.7.nupkg and /dev/null differ diff --git a/.github/workflows/dependencies/dotnet-whatsnew.2.2.0.nupkg b/.github/workflows/dependencies/dotnet-whatsnew.2.2.0.nupkg new file mode 100644 index 0000000000000..b538f4f213e22 Binary files /dev/null and b/.github/workflows/dependencies/dotnet-whatsnew.2.2.0.nupkg differ diff --git a/.github/workflows/docs-verifier-tryfix.yml b/.github/workflows/docs-verifier-tryfix.yml index 676196d194a3f..caa1b79341e9f 100644 --- a/.github/workflows/docs-verifier-tryfix.yml +++ b/.github/workflows/docs-verifier-tryfix.yml @@ -13,34 +13,34 @@ jobs: IS_TRY_FIX: true # differentiates /tryfix from the validation-only run. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/github-script@v3 - id: get-pr - with: - script: | - const request = { - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number - } - core.info(`Getting PR #${request.pull_number} from ${request.owner}/${request.repo}`) - try { - const result = await github.pulls.get(request) - return result.data - } catch (err) { - core.setFailed(`Request failed with error ${err}`) - } - - name: Checkout the repository - uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 #@v2 + - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 + id: get-pr + with: + script: | + const request = { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + } + core.info(`Getting PR #${request.pull_number} from ${request.owner}/${request.repo}`) + try { + const result = await github.pulls.get(request) + return result.data + } catch (err) { + core.setFailed(`Request failed with error ${err}`) + } + - name: Checkout the repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - - name: Checkout Pull Request - run: | - hub pr checkout ${{ github.event.issue.number }} + - name: Checkout Pull Request + run: | + hub pr checkout ${{ github.event.issue.number }} - - name: Tryfix - uses: dotnet/docs-actions/actions/docs-verifier@main + - name: Tryfix + uses: dotnet/docs-actions/actions/docs-verifier@main - - name: Push changes - run: | + - name: Push changes + run: | git config --global user.name github-actions git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com git remote add fork https://github.com/${{ fromJSON(steps.get-pr.outputs.result).head.repo.full_name }} diff --git a/.github/workflows/docs-verifier.yml b/.github/workflows/docs-verifier.yml index fc6f0455212ce..2698d103986c7 100644 --- a/.github/workflows/docs-verifier.yml +++ b/.github/workflows/docs-verifier.yml @@ -8,11 +8,9 @@ jobs: validate: name: MSDocs build verifier runs-on: ubuntu-latest - permissions: - statuses: write steps: - - name: Checkout the repository - uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 #@v2 + - name: Checkout the repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - - name: Validate - uses: dotnet/docs-actions/actions/docs-verifier@main + - name: Validate + uses: dotnet/docs-actions/actions/docs-verifier@main diff --git a/.github/workflows/live-protection.yml b/.github/workflows/live-protection.yml index 4ce00baac317d..c62282bc32298 100644 --- a/.github/workflows/live-protection.yml +++ b/.github/workflows/live-protection.yml @@ -4,7 +4,7 @@ jobs: comment: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v4 + - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 env: SHOULD_COMMENT: ${{ github.base_ref == 'refs/heads/live' && !(github.event.issue.user.login == 'cxwtool' || github.head_ref == 'refs/heads/main') }} with: diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 35a8be4a9954c..74e3694f7cea4 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -6,31 +6,30 @@ on: - main paths: - "**/*.md" - - ".markdownlint.json" + - ".markdownlint-cli2.jsonc" - ".github/workflows/markdownlint.yml" - ".github/workflows/markdownlint-problem-matcher.json" pull_request: paths: - "**/*.md" - - ".markdownlint.json" + - ".markdownlint-cli2.jsonc" - ".github/workflows/markdownlint.yml" - ".github/workflows/markdownlint-problem-matcher.json" jobs: lint: - runs-on: ubuntu-latest permissions: - statuses: write + statuses: write steps: - - uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 #@v2 - - name: Use Node.js - uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d #@v1 - with: - node-version: 12.x - - name: Run Markdownlint - run: | - echo "::add-matcher::.github/workflows/markdownlint-problem-matcher.json" - npm i -g markdownlint-cli - markdownlint "**/*.md" + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - name: Use Node.js + uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 + with: + node-version: 16.x + - name: Run Markdownlint + run: | + echo "::add-matcher::.github/workflows/markdownlint-problem-matcher.json" + npm i -g markdownlint-cli2 + markdownlint-cli2 "**/*.md" diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index ac67b22d4be8f..41ce21dc8c8ab 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -6,11 +6,14 @@ on: issue_comment: types: [created] schedule: - # Schedule every day at 00:05 - - cron: '5 0 * * *' + # Schedule for five minutes after the hour, every hour + - cron: '5 * * * *' jobs: noResponse: + permissions: + issues: write + pull-requests: write runs-on: ubuntu-latest steps: - uses: lee-dohm/no-response@9bb0a4b5e6a45046f00353d5de7d90fb8bd773bb diff --git a/.github/workflows/quest-bulk.yml b/.github/workflows/quest-bulk.yml new file mode 100644 index 0000000000000..52c3c7ac684ec --- /dev/null +++ b/.github/workflows/quest-bulk.yml @@ -0,0 +1,34 @@ +name: "bulk quest import" +on: + workflow_dispatch: + inputs: + reason: + description: "The reason for running the bulk import workflow" + required: true + default: "Initial import into Quest (Azure DevOps)" + +jobs: + bulk-import: + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + + steps: + - name: "Print manual bulk import run reason" + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo "Reason: ${{ github.event.inputs.reason }}" + + - name: bulk-sequester + if: ${{ github.event_name == 'workflow_dispatch' }} + id: bulk-sequester + uses: dotnet/docs-tools/actions/sequester@main + env: + ImportOptions__ApiKeys__GitHubToken: ${{ secrets.GITHUB_TOKEN }} + ImportOptions__ApiKeys__OSPOKey: ${{ secrets.OSPO_KEY }} + ImportOptions__ApiKeys__QuestKey: ${{ secrets.QUEST_KEY }} + with: + org: ${{ github.repository_owner }} + repo: ${{ github.repository }} + issue: '-1' diff --git a/.github/workflows/quest.yml b/.github/workflows/quest.yml new file mode 100644 index 0000000000000..3d419c585ac64 --- /dev/null +++ b/.github/workflows/quest.yml @@ -0,0 +1,63 @@ +name: "quest import" +on: + issues: + types: + [ labeled, closed, reopened, assigned, unassigned ] + workflow_dispatch: + inputs: + reason: + description: "The reason for running the workflow" + required: true + default: "Manual run" + issue: + description: "The issue number to manually test" + required: true + +jobs: + import: + if: | + github.event_name == 'workflow_dispatch' || + github.event.label.name == ':world_map: reQUEST' || + github.event.label.name == ':pushpin: seQUESTered' || + contains(github.event.issue.labels.*.name, ':world_map: reQUEST') || + contains(github.event.issue.labels.*.name, ':pushpin: seQUESTered') + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + + steps: + - name: "Print manual run reason" + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo "Reason: ${{ github.event.inputs.reason }}" + echo "Issue number: ${{ github.event.inputs.issue }}" + + # This step occurs when ran manually, passing the manual issue number input + - name: manual-sequester + if: ${{ github.event_name == 'workflow_dispatch' }} + id: manual-sequester + uses: dotnet/docs-tools/actions/sequester@main + env: + ImportOptions__ApiKeys__GitHubToken: ${{ secrets.GITHUB_TOKEN }} + ImportOptions__ApiKeys__OSPOKey: ${{ secrets.OSPO_KEY }} + ImportOptions__ApiKeys__QuestKey: ${{ secrets.QUEST_KEY }} + with: + org: ${{ github.repository_owner }} + repo: ${{ github.repository }} + issue: ${{ github.event.inputs.issue }} + + # This step occurs automatically, passing the issue number from the event + - name: auto-sequester + if: ${{ github.event_name != 'workflow_dispatch' }} + id: auto-sequester + uses: dotnet/docs-tools/actions/sequester@main + env: + ImportOptions__ApiKeys__GitHubToken: ${{ secrets.GITHUB_TOKEN }} + ImportOptions__ApiKeys__OSPOKey: ${{ secrets.OSPO_KEY }} + ImportOptions__ApiKeys__QuestKey: ${{ secrets.QUEST_KEY }} + with: + org: ${{ github.repository_owner }} + repo: ${{ github.repository }} + issue: ${{ github.event.issue.number }} + diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000000..202284c1385cc --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,27 @@ +name: Close stale issues + +on: + schedule: # Run 5 minutes after midnight daily. + - cron: '5 0 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 + with: + start-date: '2022-03-01T00:00:00Z' # ISO 8601 or RFC 2822 + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 180 + days-before-close: 14 + stale-issue-label: stale + close-issue-message: > + This issue has been automatically closed due to lack of activity. + If you feel this issue is still important, please reopen it and leave a comment. + exempt-issue-labels: 'breaking-change,Pri0,:watch: Not Triaged,:checkered_flag: Release: .NET 7,:checkered_flag: Release: .NET 8' + operations-per-run: 50 + debug-only: true diff --git a/.github/workflows/version-sweep.yml b/.github/workflows/version-sweep.yml index b35789a04f852..c653cbf810042 100644 --- a/.github/workflows/version-sweep.yml +++ b/.github/workflows/version-sweep.yml @@ -1,18 +1,18 @@ # This is a basic workflow to help you get started with Actions -name: 'target supported version' +name: "target supported version" -# Controls when the action will run. +# Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the default branch schedule: - - cron: '0 0 1 * *' + - cron: "0 0 1 * *" workflow_dispatch: inputs: reason: - description: 'The reason for running the workflow' + description: "The reason for running the workflow" required: true - default: 'Manual run' + default: "Manual run" # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -27,10 +27,10 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # Runs a single command using the runners shell - - name: 'Print manual run reason' + - name: "Print manual run reason" if: ${{ github.event_name == 'workflow_dispatch' }} run: | echo 'Reason: ${{ github.event.inputs.reason }}' diff --git a/.github/workflows/whats-new.yml b/.github/workflows/whats-new.yml index db57356f5dd1c..b0428e0ab28d2 100644 --- a/.github/workflows/whats-new.yml +++ b/.github/workflows/whats-new.yml @@ -1,21 +1,21 @@ # This is a basic workflow to help you get started with Actions -name: 'generate what''s new article' +name: "generate what's new article" -# Controls when the action will run. +# Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the default branch schedule: - - cron: '0 0 1 * *' # The first of every month + - cron: "0 0 1 * *" # The first of every month workflow_dispatch: inputs: reason: - description: 'The reason for running the workflow' + description: "The reason for running the workflow" required: true - default: 'Manual run' + default: "Manual run" env: - DOTNET_VERSION: '6.0.x' # set this to the dot net version to use + DOTNET_VERSION: "6.0.x" # set this to the dot net version to use # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -30,14 +30,14 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a with: dotnet-version: ${{ env.DOTNET_VERSION }} # Runs a single command using the runners shell - - name: 'Print manual run reason' + - name: "Print manual run reason" if: ${{ github.event_name == 'workflow_dispatch' }} run: | echo "Reason: ${{ github.event.inputs.reason }}" @@ -62,8 +62,8 @@ jobs: # Create the PR for the new article - name: create-pull-request - uses: peter-evans/create-pull-request@v3.11.0 + uses: peter-evans/create-pull-request@b4d51739f96fca8047ad065eccef63442d8e99f7 with: - title: 'What''s new article' + title: "What's new article" commit-message: 'Bot 🤖 generated "What''s new article"' - body: 'Automated creation of What''s new article.' + body: "Automated creation of What's new article." diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000000000..5d410e977fba7 --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,67 @@ +{ + "config": { + "default": true, + // Rule is fixable, but corrections may have impacts on headings showing up in sidebars + "MD001": false, + // Rule is auto-fixable, and should pick single style + "MD004": false, + // Disabled as word wraping can cause many diffs + "MD013": false, + // Currently broken with custom tab solution + "MD024": false, + // Currently broken with custom tab solution + "MD025": false, + "MD026": false, + // Cannot be enabled due to sequential Note blocks + "MD028": false, + "MD033": { + "allowed_elements": [ + "a", + "blockquote", + "br", + "code", + "em", + "h4", + "kbd", + "li", + "ol", + "p", + "pre", + "span", + "sub", + "sup", + "table", + "tbody", + "td", + "th", + "thead", + "tr", + "u", + "ul" + ] + }, + "MD036": false, + "MD040": false, + // Cannot be enabled because of the include folder snippets + "MD041": false, + "MD044": { + "code_blocks": false, + "names": [ + "ASP.NET", + "PowerShell", + "Orleans" + ] + }, + "MD046": { + "style": "fenced" + }, + "MD049": false, + "MD050": { + "style": "asterisk" + }, + "MD051": false + }, + "ignores": [ + ".github/" + ] +} diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index 5e1c2cbe37caa..0000000000000 --- a/.markdownlint.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "default": true, - "MD001": false, - "MD004": false, - "MD013": false, - "MD024": false, - "MD025": false, - "MD026": false, - "MD028": false, - "MD033": { - "allowed_elements": [ - "a", - "blockquote", - "br", - "code", - "em", - "h4", - "kbd", - "li", - "ol", - "p", - "pre", - "span", - "sub", - "sup", - "table", - "tbody", - "td", - "th", - "thead", - "tr", - "u", - "ul" - ] - }, - "MD036": false, - "MD040": false, - "MD041": false, - "MD046": { - "style": "fenced" - }, - "MD049": false, - "MD050": false, - "MD051": false, - "proper-names": { - "code_blocks": false, - "names": [ - "ASP.NET", - "PowerShell", - "Orleans" - ] - } -} diff --git a/.openpublishing.redirection.core.json b/.openpublishing.redirection.core.json index ae8126eaf3c37..28d0eb06e6a06 100644 --- a/.openpublishing.redirection.core.json +++ b/.openpublishing.redirection.core.json @@ -57,6 +57,10 @@ "source_path_from_root": "/docs/core/compatibility/aspnet-core/6.0/blazor-long-polling-fallback.md", "redirect_url": "/dotnet/core/compatibility/6.0" }, + { + "source_path_from_root": "/docs/core/compatibility/aspnet-core/7.0/iendpointmetadataprovider-changes.md", + "redirect_url": "/dotnet/core/compatibility/7.0" + }, { "source_path_from_root": "/docs/core/compatibility/code-analysis.md", "redirect_url": "/dotnet/core/compatibility/code-analysis/5.0/ca1416-platform-compatibility-analyzer" @@ -107,6 +111,10 @@ "redirect_url": "/dotnet/core/compatibility/sdk/6.0/gettargetframeworkproperties-and-getnearesttargetframework-removed", "redirect_document_id": true }, + { + "source_path_from_root": "/docs/core/compatibility/networking/7.0/connectasync-argumentexception.md", + "redirect_url": "/dotnet/core/compatibility/7.0" + }, { "source_path_from_root": "/docs/core/compatibility/sdk/6.0/implicit-namespaces.md", "redirect_url": "/dotnet/core/compatibility/sdk/6.0" @@ -299,6 +307,11 @@ "redirect_url": "/dotnet/core/deploying/trimming/fixing-warnings", "redirect_document_id": true }, + { + "source_path_from_root": "/docs/core/deploying/native-aot.md", + "redirect_url": "/dotnet/core/deploying/native-aot/index", + "redirect_document_id": true + }, { "source_path_from_root": "/docs/core/deploying/prepare-libraries-for-trimming.md", "redirect_url": "/dotnet/core/deploying/trimming/prepare-libraries-for-trimming", @@ -1100,7 +1113,7 @@ }, { "source_path_from_root": "/docs/core/whats-new/index.md", - "redirect_url": "/dotnet/core/whats-new/dotnet-core-3-1", + "redirect_url": "/dotnet/core/whats-new/dotnet-7", "ms.custom": "updateeachrelease" }, { @@ -1114,6 +1127,18 @@ { "source_path_from_root": "/docs/core/tools/using-ci-with-cli.md", "redirect_url": "/dotnet/devops/dotnet-cli-and-continuous-integration" + }, + { + "source_path_from_root": "/docs/core/extensions/http-client.md", + "redirect_url": "/dotnet/core/extensions/http-client-factory" + }, + { + "source_path_from_root": "/docs/fundamentals/networking/tcp/tcp-services.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/tcp-classes" + }, + { + "source_path_from_root": "/docs/fundamentals/networking/tcp/tcp-overview.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/tcp-classes" } ] } diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index cef1e9d684068..1c7fa9abd97af 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -8,10 +8,34 @@ "source_path_from_root": "/_csharplang/proposals/csharp-7.0/digit-separators.md", "redirect_url": "/dotnet/csharp/language-reference/language-specification/lexical-structure#6453-integer-literals" }, + { + "source_path_from_root": "/_csharplang/proposals/csharp-7.0/local-functions.md", + "redirect_url": "/dotnet/csharp/language-reference/language-specification/statements.md#1264-local-function-declarations" + }, + { + "source_path_from_root": "/_csharplang/proposals/csharp-7.0/throw-expression.md", + "redirect_url": "/dotnet/csharp/language-reference/language-specification/expressions.md#1115-the-throw-expression-operator" + }, + { + "source_path_from_root": "/_csharplang/proposals/csharp-7.1/async-main.md", + "redirect_url": "/dotnet/csharp/language-reference/language-specification/basic-concepts#71-application-startup" + }, + { + "source_path_from_root": "/_csharplang/proposals/csharp-7.2/private-protected.md", + "redirect_url": "/dotnet/csharp/language-reference/language-specification/classes.md#1436-access-modifiers" + }, + { + "source_path_from_root": "/_csharplang/proposals/csharp-7.2/readonly-struct.md", + "redirect_url": "/dotnet/csharp/language-reference/language-specification/structs.md#1524-struct-interfaces" + }, { "source_path_from_root": "/_csharplang/proposals/csharp-7.3/leading-digit-separator.md", "redirect_url": "/dotnet/csharp/language-reference/language-specification/lexical-structure#6453-integer-literals" }, + { + "source_path_from_root": "/_csharplang/proposals/csharp-10.0/generic-attributes.md", + "redirect_url": "/dotnet/csharp/language-reference/proposals/csharp-11.0/generic-attributes" + }, { "source_path_from_root": "/docs/csharp/basic-types.md", "redirect_url": "/dotnet/csharp/fundamentals/types" @@ -111,7 +135,7 @@ }, { "source_path_from_root": "/docs/csharp/getting-started/whats-new.md", - "redirect_url": "/dotnet/csharp/whats-new/csharp-10" + "redirect_url": "/dotnet/csharp/whats-new/csharp-11" }, { "source_path_from_root": "/docs/csharp/getting-started/with-visual-studio-2017.md", @@ -455,6 +479,14 @@ "source_path_from_root": "/docs/csharp/language-reference/keywords/char.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/char" }, + { + "source_path_from_root": "/docs/csharp/language-reference/keywords/checked.md", + "redirect_url": "/dotnet/csharp/language-reference/statements/checked-and-unchecked" + }, + { + "source_path_from_root": "/docs/csharp/language-reference/keywords/checked-and-unchecked.md", + "redirect_url": "/dotnet/csharp/language-reference/statements/checked-and-unchecked" + }, { "source_path_from_root": "/docs/csharp/language-reference/keywords/continue.md", "redirect_url": "/dotnet/csharp/language-reference/statements/jump-statements" @@ -519,6 +551,10 @@ "source_path_from_root": "/docs/csharp/language-reference/keywords/false.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/bool" }, + { + "source_path_from_root": "/docs/csharp/language-reference/keywords/fixed-statement.md", + "redirect_url": "/dotnet/csharp/language-reference/statements/fixed" + }, { "source_path_from_root": "/docs/csharp/language-reference/keywords/float.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types" @@ -699,6 +735,10 @@ "source_path_from_root": "/docs/csharp/language-reference/keywords/ulong.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/integral-numeric-types" }, + { + "source_path_from_root": "/docs/csharp/language-reference/keywords/unchecked.md", + "redirect_url": "/dotnet/csharp/language-reference/statements/checked-and-unchecked" + }, { "source_path_from_root": "/docs/csharp/language-reference/keywords/ushort.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/integral-numeric-types" @@ -715,6 +755,10 @@ "source_path_from_root": "/docs/csharp/language-reference/keywords/value-types.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/value-types" }, + { + "source_path_from_root": "/docs/csharp/language-reference/keywords/var.md", + "redirect_url": "/dotnet/csharp/language-reference/statements/declarations#implicitly-typed-local-variables" + }, { "source_path_from_root": "/docs/csharp/language-reference/keywords/void.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/void" @@ -723,6 +767,10 @@ "source_path_from_root": "/docs/csharp/language-reference/keywords/while.md", "redirect_url": "/dotnet/csharp/language-reference/statements/iteration-statements" }, + { + "source_path_from_root": "/docs/csharp/language-reference/keywords/yield.md", + "redirect_url": "/dotnet/csharp/language-reference/statements/yield" + }, { "source_path_from_root": "/docs/csharp/language-reference/language-specification/index.md", "redirect_url": "/dotnet/csharp/language-reference/language-specification/introduction" @@ -966,7 +1014,7 @@ }, { "source_path_from_root": "/docs/csharp/language-reference/proposals/csharp-7.1/index.md", - "redirect_url": "/dotnet/csharp/language-reference/proposals/csharp-7.1/async-main" + "redirect_url": "/dotnet/csharp/language-reference/proposals/csharp-7.1/target-typed-default" }, { "source_path_from_root": "/docs/csharp/language-reference/proposals/csharp-7.2/index.md", @@ -981,9 +1029,17 @@ "redirect_url": "/dotnet/csharp/language-reference/proposals/csharp-8.0/nullable-reference-types" }, { - "source_path_from_root": "/docs/csharp/language-reference/proposals/index.md", + "source_path_from_root": "/docs/csharp/language-reference/proposals/csharp-9.0/index.md", "redirect_url": "/dotnet/csharp/language-reference/proposals/csharp-9.0/records" }, + { + "source_path_from_root": "/docs/csharp/language-reference/proposals/csharp-10.0/index.md", + "redirect_url": "/dotnet/csharp/language-reference/proposals/csharp-10.0/record-structs" + }, + { + "source_path_from_root": "/docs/csharp/language-reference/proposals/csharp-11.0/index.md", + "redirect_url": "/dotnet/csharp/language-reference/proposals/csharp-11.0/static-abstracts-in-interfaces" + }, { "source_path_from_root": "/docs/csharp/local-functions-vs-lambdas.md", "redirect_url": "/dotnet/csharp/programming-guide/classes-and-structs/local-functions" @@ -1036,6 +1092,10 @@ "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/how-to-access-a-collection-class-with-foreach.md", "redirect_url": "/dotnet/csharp/language-reference/statements/iteration-statements" }, + { + "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/how-to-know-the-difference-passing-a-struct-and-passing-a-class-to-a-method.md", + "redirect_url": "/dotnet/csharp/language-reference/keywords/method-parameters" + }, { "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/index.md", "redirect_url": "/dotnet/csharp/fundamentals/object-oriented" @@ -1048,6 +1108,18 @@ "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/objects.md", "redirect_url": "/dotnet/csharp/fundamentals/object-oriented/objects" }, + { + "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/passing-parameters.md", + "redirect_url": "/dotnet/csharp/language-reference/keywords/method-parameters" + }, + { + "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/passing-reference-type-parameters.md", + "redirect_url": "/dotnet/csharp/language-reference/keywords/method-parameters" + }, + { + "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/passing-value-type-parameters.md", + "redirect_url": "/dotnet/csharp/language-reference/keywords/method-parameters" + }, { "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/polymorphism.md", "redirect_url": "/dotnet/csharp/fundamentals/object-oriented/polymorphism" @@ -1056,6 +1128,10 @@ "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/records.md", "redirect_url": "/dotnet/csharp/fundamentals/types/records" }, + { + "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/ref-returns.md", + "redirect_url": "/dotnet/csharp/language-reference/statements/declarations" + }, { "source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/structs.md", "redirect_url": "/dotnet/csharp/language-reference/builtin-types/struct" @@ -2664,10 +2740,6 @@ "source_path_from_root": "/docs/csharp/tutorials/exploration/top-level-statements.md", "redirect_url": "/dotnet/csharp/whats-new/tutorials/top-level-statements" }, - { - "source_path_from_root": "/docs/csharp/tutorials/generate-consume-asynchronous-stream.md", - "redirect_url": "/dotnet/csharp/whats-new/tutorials/generate-consume-asynchronous-stream" - }, { "source_path_from_root": "/docs/csharp/tutorials/index.md", "redirect_url": "/dotnet/csharp/fundamentals/tutorials/how-to-display-command-line-arguments" @@ -2732,22 +2804,10 @@ "source_path_from_root": "/docs/csharp/tutorials/microservices.md", "redirect_url": "/dotnet/core/docker/" }, - { - "source_path_from_root": "/docs/csharp/tutorials/mixins-with-default-interface-methods.md", - "redirect_url": "/dotnet/csharp/whats-new/tutorials/mixins-with-default-interface-methods" - }, - { - "source_path_from_root": "/docs/csharp/tutorials/nullable-reference-types.md", - "redirect_url": "/dotnet/csharp/whats-new/tutorials/nullable-reference-types" - }, { "source_path_from_root": "/docs/csharp/tutorials/pattern-matching.md", "redirect_url": "/dotnet/csharp/fundamentals/tutorials/pattern-matching" }, - { - "source_path_from_root": "/docs/csharp/tutorials/ranges-indexes.md", - "redirect_url": "/dotnet/csharp/whats-new/tutorials/ranges-indexes" - }, { "source_path_from_root": "/docs/csharp/tutorials/upgrade-to-nullable-references.md", "redirect_url": "/dotnet/csharp/nullable-migration-strategies" @@ -2762,7 +2822,7 @@ }, { "source_path_from_root": "/docs/csharp/whats-new.md", - "redirect_url": "/dotnet/csharp/whats-new/csharp-10", + "redirect_url": "/dotnet/csharp/whats-new/csharp-11", "redirect_document_id": true }, { @@ -2785,14 +2845,38 @@ "source_path_from_root": "/docs/csharp/whats-new/csharp-7.md", "redirect_url": "/dotnet/csharp/whats-new/csharp-version-history#c-version-70" }, + { + "source_path_from_root": "/docs/csharp/whats-new/csharp-8.md", + "redirect_url": "/dotnet/csharp/whats-new/csharp-version-history#c-version-80" + }, { "source_path_from_root": "/docs/csharp/whats-new/index.md", - "redirect_url": "/dotnet/csharp/whats-new/csharp-10", + "redirect_url": "/dotnet/csharp/whats-new/csharp-11", "ms.custom": "updateeachrelease" }, { "source_path_from_root": "/docs/csharp/whats-new/tutorials/upgrade-to-nullable-references.md", "redirect_url": "/dotnet/csharp/nullable-migration-strategies" + }, + { + "source_path_from_root": "/docs/csharp/whats-new/tutorials/default-interface-methods-versions.md", + "redirect_url": "/dotnet/csharp/tutorials/default-interface-methods-versions" + }, + { + "source_path_from_root": "/docs/csharp/whats-new/tutorials/generate-consume-asynchronous-stream.md", + "redirect_url": "/dotnet/csharp/tutorials/generate-consume-asynchronous-stream" + }, + { + "source_path_from_root": "/docs/csharp/whats-new/tutorials/mixins-with-default-interface-methods.md", + "redirect_url": "/dotnet/csharp/tutorials/mixins-with-default-interface-methods" + }, + { + "source_path_from_root": "/docs/csharp/whats-new/tutorials/nullable-reference-types.md", + "redirect_url": "/dotnet/csharp/tutorials/nullable-reference-types" + }, + { + "source_path_from_root": "/docs/csharp/whats-new/tutorials/ranges-indexes.md", + "redirect_url": "/dotnet/csharp/tutorials/ranges-indexes" } ] } diff --git a/.openpublishing.redirection.framework.json b/.openpublishing.redirection.framework.json index 319e7d1e5b7ff..57ab29e1b621d 100644 --- a/.openpublishing.redirection.framework.json +++ b/.openpublishing.redirection.framework.json @@ -2388,6 +2388,250 @@ { "source_path_from_root": "/docs/framework/xaml-services/{}-escape-sequence-markup-extension.md", "redirect_url": "/dotnet/desktop/xaml-services/escape-sequence-markup-extension" + }, + { + "source_path_from_root": "/docs/framework/network-programming/index.md", + "redirect_url": "/dotnet/fundamentals/networking/overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/introducing-pluggable-protocols.md", + "redirect_url": "/dotnet/fundamentals/networking/overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/network-programming-how-to-topics.md", + "redirect_url": "/dotnet/fundamentals/networking/overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/requesting-data.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/creating-internet-requests.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-request-a-web-page-and-retrieve-the-results-as-a-stream.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-request-data-using-the-webrequest-class.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-send-data-using-the-webrequest-class.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-retrieve-a-protocol-specific-webresponse-that-matches-a-webrequest.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-streams-on-the-network.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/making-asynchronous-requests.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/handling-errors.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient#http-response-errors" + }, + { + "source_path_from_root": "/docs/framework/network-programming/programming-pluggable-protocols.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-register-a-custom-protocol-using-webrequest.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-typecast-a-webrequest-to-access-protocol-specific-properties.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/deriving-from-webrequest.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/deriving-from-webresponse.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-application-protocols.md", + "redirect_url": "/dotnet/fundamentals/networking/overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/http.md", + "redirect_url": "/dotnet/fundamentals/networking/http/http-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-access-http-specific-properties.md", + "redirect_url": "/dotnet/fundamentals/networking/http/http-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/managing-connections.md", + "redirect_url": "/dotnet/fundamentals/networking/http/http-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/connection-grouping.md", + "redirect_url": "/dotnet/fundamentals/networking/http/http-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-assign-user-information-to-group-connections.md", + "redirect_url": "/dotnet/fundamentals/networking/http/http-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/tcp-udp.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/tcp-classes" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-tcp-services.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/tcp-classes" + }, + { + "source_path_from_root": "/docs/framework/network-programming/sockets.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/sockets-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-a-synchronous-client-socket.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-client" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-a-synchronous-server-socket.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-server" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-an-asynchronous-client-socket.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-client" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-an-asynchronous-server-socket.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-server" + }, + { + "source_path_from_root": "/docs/framework/network-programming/synchronous-client-socket-example.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-client" + }, + { + "source_path_from_root": "/docs/framework/network-programming/synchronous-server-socket-example.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-server" + }, + { + "source_path_from_root": "/docs/framework/network-programming/asynchronous-client-socket-example.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-client" + }, + { + "source_path_from_root": "/docs/framework/network-programming/asynchronous-server-socket-example.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-server" + }, + { + "source_path_from_root": "/docs/framework/network-programming/listening-with-sockets.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-server" + }, + { + "source_path_from_root": "/docs/framework/network-programming/socket-code-examples.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/sockets-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/socket-performance-enhancements-in-version-3-5.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/sockets-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/network-programming-samples.md", + "redirect_url": "/dotnet/fundamentals/networking/overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/network-isolation-for-windows-store-apps.md", + "redirect_url": "/dotnet/fundamentals/networking/overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/understanding-webrequest-problems-and-exceptions.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient#http-response-errors" + }, + { + "source_path_from_root": "/docs/framework/network-programming/using-client-sockets.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services#create-a-socket-client" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-create-a-socket.md", + "redirect_url": "/dotnet/fundamentals/networking/sockets/socket-services" + }, + { + "source_path_from_root": "/docs/framework/network-programming/internet-protocol-version-6.md", + "redirect_url": "/dotnet/fundamentals/networking/ipv6-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/ipv6-addressing.md", + "redirect_url": "/dotnet/fundamentals/networking/ipv6-overview#ipv6-addressing" + }, + { + "source_path_from_root": "/docs/framework/network-programming/ipv6-routing.md", + "redirect_url": "/dotnet/fundamentals/networking/ipv6-overview#ipv6-routing" + }, + { + "source_path_from_root": "/docs/framework/network-programming/ipv6-auto-configuration.md", + "redirect_url": "/dotnet/fundamentals/networking/ipv6-overview#ipv6-auto-configuration" + }, + { + "source_path_from_root": "/docs/framework/network-programming/enabling-and-disabling-ipv6.md", + "redirect_url": "/dotnet/fundamentals/networking/ipv6-overview#disable-or-enable-ipv6" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-modify-the-computer-configuration-file-to-enable-ipv6-support.md", + "redirect_url": "/dotnet/fundamentals/networking/ipv6-overview#disable-or-enable-ipv6" + }, + { + "source_path_from_root": "/docs/framework/network-programming/networkinformation.md", + "redirect_url": "/dotnet/fundamentals/networking/network-info" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-detect-network-availability-and-address-changes.md", + "redirect_url": "/dotnet/fundamentals/networking/network-info#network-change-events" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-get-interface-and-protocol-information.md", + "redirect_url": "/dotnet/fundamentals/networking/network-info#network-statistics-and-properties" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-ping-a-host.md", + "redirect_url": "/dotnet/fundamentals/networking/network-info#determine-if-a-remote-host-is-reachable" + }, + { + "source_path_from_root": "/docs/framework/network-programming/accessing-the-internet-through-a-proxy.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient#http-proxy" + }, + { + "source_path_from_root": "/docs/framework/network-programming/automatic-proxy-detection.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient#http-proxy" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-override-a-global-proxy-selection.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient#global-default-proxy" + }, + { + "source_path_from_root": "/docs/framework/network-programming/international-resource-identifier-support-in-system-uri.md", + "redirect_url": "/dotnet/fundamentals/networking/overview#identifying-resources" + }, + { + "source_path_from_root": "/docs/framework/network-programming/nat-traversal-using-ipv6-and-teredo.md", + "redirect_url": "/dotnet/fundamentals/networking/ipv6-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/proxy-configuration.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient#http-proxy" + }, + { + "source_path_from_root": "/docs/framework/network-programming/changes-to-the-system-uri-namespace-in-version-2-0.md", + "redirect_url": "/dotnet/fundamentals/networking/overview#identifying-resources" + }, + { + "source_path_from_root": "/docs/framework/network-programming/changes-to-ntlm-authentication-for-httpwebrequest-in-version-3-5-sp1.md", + "redirect_url": "/dotnet/fundamentals/networking/http/http-overview" + }, + { + "source_path_from_root": "/docs/framework/network-programming/how-to-enable-a-webrequest-to-use-a-proxy-to-communicate-with-the-internet.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient#http-proxy" } ] } diff --git a/.openpublishing.redirection.fsharp.json b/.openpublishing.redirection.fsharp.json index 975feb8968955..2af7f74891c0d 100644 --- a/.openpublishing.redirection.fsharp.json +++ b/.openpublishing.redirection.fsharp.json @@ -148,7 +148,7 @@ }, { "source_path_from_root": "/docs/fsharp/whats-new/index.md", - "redirect_url": "/dotnet/fsharp/whats-new/fsharp-47", + "redirect_url": "/dotnet/fsharp/whats-new/fsharp-6", "ms.custom": "updateeachrelease" } ] diff --git a/.openpublishing.redirection.fundamentals.json b/.openpublishing.redirection.fundamentals.json index 082c85dce03de..699bd2fac6ad9 100644 --- a/.openpublishing.redirection.fundamentals.json +++ b/.openpublishing.redirection.fundamentals.json @@ -100,6 +100,14 @@ "source_path_from_root": "/docs/fundamentals/code-analysis/style-rules/ide1006.md", "redirect_url": "/dotnet/fundamentals/code-analysis/style-rules/naming-rules" }, + { + "source_path_from_root": "/docs/fundamentals/networking/httpclient.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient" + }, + { + "source_path_from_root": "/docs/fundamentals/networking/httpclient-guidelines.md", + "redirect_url": "/dotnet/fundamentals/networking/http/httpclient-guidelines" + }, { "source_path_from_root": "/docs/fundamentals/productivity/code-analysis.md", "redirect_url": "/dotnet/fundamentals/code-analysis/overview" diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index ac33ac33de873..0370d7eac7b68 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -2,7 +2,7 @@ "redirections": [ { "source_path_from_root": "/docs/about/index.md", - "redirect_url": "/dotnet/standard/index" + "redirect_url": "/dotnet/fundamentals/" }, { "source_path_from_root": "/docs/about/products.md", @@ -66,7 +66,7 @@ }, { "source_path_from_root": "/docs/standard/about.md", - "redirect_url": "/dotnet/standard/index" + "redirect_url": "/dotnet/fundamentals/" }, { "source_path_from_root": "/docs/tutorials/getting-started-with-csharp/microservices.md", @@ -79,6 +79,18 @@ { "source_path_from_root": "/docs/tutorials/index.md", "redirect_url": "/dotnet/samples-and-tutorials/" + }, + { + "source_path_from_root": "/docs/whats-new/dotnet-docs-2022-08-01.md", + "redirect_url": "/dotnet/whats-new/dotnet-docs-mod2" + }, + { + "source_path_from_root": "/docs/whats-new/dotnet-docs-2022-09-01.md", + "redirect_url": "/dotnet/whats-new/dotnet-docs-mod0" + }, + { + "source_path_from_root": "/docs/whats-new/dotnet-docs-2022-10-01.md", + "redirect_url": "/dotnet/whats-new/dotnet-docs-mod1" } ] } diff --git a/.openpublishing.redirection.standard.json b/.openpublishing.redirection.standard.json index 690b730a462a0..39cd14e23f4a2 100644 --- a/.openpublishing.redirection.standard.json +++ b/.openpublishing.redirection.standard.json @@ -11,7 +11,7 @@ }, { "source_path_from_root": "/docs/standard/application-essentials.md", - "redirect_url": "/dotnet/standard/index" + "redirect_url": "/dotnet/fundamentals/" }, { "source_path_from_root": "/docs/standard/assembly-format.md", @@ -627,7 +627,97 @@ }, { "source_path_from_root": "/docs/standard/serialization/write-custom-serializer-deserializer.md", - "redirect_url": "/dotnet/standard/serialization/system-text-json-use-dom-utf8jsonreader-utf8jsonwriter" + "redirect_url": "/dotnet/standard/serialization/system-text-json/use-dom-utf8jsonreader-utf8jsonwriter" + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-character-casing.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/character-casing", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-character-encoding.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/character-encoding", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-configure-options.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/configure-options", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-converters-how-to.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/converters-how-to", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-customize-properties.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/customize-properties", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-handle-overflow.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/handle-overflow", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-how-to.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/how-to", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-ignore-properties.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/ignore-properties", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-immutability.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/immutability", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-invalid-json.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/invalid-json", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-overview.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/overview", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-polymorphism.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/polymorphism", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-preserve-references.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/preserve-references", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-source-generation-modes.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/source-generation-modes", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-source-generation.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/source-generation", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-supported-collection-types.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/supported-collection-types", + "redirect_document_id": true + }, + { + "source_path_from_root": "/docs/standard/serialization/system-text-json-use-dom-utf8jsonreader-utf8jsonwriter.md", + "redirect_url": "/dotnet/standard/serialization/system-text-json/use-dom-utf8jsonreader-utf8jsonwriter", + "redirect_document_id": true }, { "source_path_from_root": "/docs/standard/threading/autoresetevent.md", diff --git a/.openpublishing.redirection.visual-basic.json b/.openpublishing.redirection.visual-basic.json index 301d4aabfd627..38df2ae084b74 100644 --- a/.openpublishing.redirection.visual-basic.json +++ b/.openpublishing.redirection.visual-basic.json @@ -171,11 +171,11 @@ }, { "source_path_from_root": "/docs/visual-basic/getting-started/breaking-changes-in-visual-studio-2015.md", - "redirect_url": "/visualstudio/porting/porting-migrating-and-upgrading-visual-studio-projects?view=vs-2015" + "redirect_url": "/previous-versions/visualstudio/visual-studio-2015/porting/porting-migrating-and-upgrading-visual-studio-projects?view=vs-2015" }, { "source_path_from_root": "/docs/visual-basic/getting-started/breaking-changes-in-visual-studio.md", - "redirect_url": "/visualstudio/porting/porting-migrating-and-upgrading-visual-studio-projects?view=vs-2015" + "redirect_url": "/previous-versions/visualstudio/visual-studio-2015/porting/porting-migrating-and-upgrading-visual-studio-projects" }, { "source_path_from_root": "/docs/visual-basic/getting-started/whats-new.md", diff --git a/.repoman.yml b/.repoman.yml index 78eaf0dc20647..b407198ddcb53 100644 --- a/.repoman.yml +++ b/.repoman.yml @@ -43,19 +43,6 @@ issues: pass: - labels-add: ["source incompatible"] - # Checks breaking change version and adds a label - - check: - - type: query - value: "contains(Issue.body, '.NET 6 Preview') == `true` || contains(Issue.body, '.NET 6 RC') == `true` || contains(Issue.body, '.NET 6 GA') == `true`" - pass: - - labels-add: [":checkered_flag: Release: .NET 6"] - - - check: - - type: query - value: "contains(Issue.body, '.NET 7 Preview') == `true` || contains(Issue.body, '.NET 7 RC') == `true` || contains(Issue.body, '.NET 7 GA') == `true`" - pass: - - labels-add: [":checkered_flag: Release: .NET 7"] - # Add to .NET 6 project if .NET 6 label added - check: - type: query @@ -308,6 +295,12 @@ pull_request: - path: "(?i).*docs\/framework\/network-programming.*" run: - labels-add: [ "dotnet-framework/prod", "dotnet-networking/tech" ] + - path: "(?i).*docs\/fundamentals\/networking.*" + run: + - labels-add: [ "dotnet-fundamentals/prod", "dotnet-networking/tech" ] + - path: "(?i).*docs\/core\/extensions\/http.*" + run: + - labels-add: [ "dotnet-fundamentals/prod", "dotnet-networking/tech" ] - path: "(?i).*docs\/framework\/wcf.*" run: - labels-add: [ "dotnet-framework/prod", "dotnet-wcf/tech" ] diff --git a/.whatsnew.json b/.whatsnew.json index b2979bd15d75e..7c426b6a1177c 100644 --- a/.whatsnew.json +++ b/.whatsnew.json @@ -8,9 +8,9 @@ }, "navigationOptions": { "maximumNumberOfArticles": 3, - "tocParentNode": "What's new", + "tocParentNode": "Latest documentation updates", "repoTocFolder": "docs/whats-new", - "indexParentNode": "Find .NET updates", + "indexParentNode": "Latest documentation updates", "repoIndexFolder": "docs/whats-new" }, "areas": [ diff --git a/.whatsnew.release.json b/.whatsnew.release.json new file mode 100644 index 0000000000000..634a77f5445a0 --- /dev/null +++ b/.whatsnew.release.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://whatsnewapi.azurewebsites.net/schema", + "docSetProductName": ".NET", + "rootDirectory": "docs/", + "docLinkSettings": { + "linkFormat": "relative", + "relativeLinkPrefix": "../" + }, + "inclusionCriteria": { + "labels": [ + "label:\":checkered_flag: Release: .NET 7\"" + ], + "maxFilesChanged": 100, + "minAdditionsToFile": 1 + }, + "navigationOptions": { + "maximumNumberOfArticles": 4, + "tocParentNode": "What's new", + "repoTocFolder": "docs/whats-new", + "indexParentNode": ".NET release updates", + "repoIndexFolder": "docs/whats-new" + }, + "areas": [ + { + "names": [ "." ], + "heading": "Home" + }, + { + "names": [ "architecture" ], + "heading": "Architecture guides" + }, + { + "names": [ "azure" ], + "heading": "Azure SDK for .NET" + }, + { + "names": [ "csharp" ], + "heading": "C# language" + }, + { + "names": [ "desktop-wpf" ], + "heading": ".NET Core desktop" + }, + { + "names": [ "framework" ], + "heading": ".NET Framework" + }, + { + "names": [ "fsharp" ], + "heading": "F# language" + }, + { + "names": [ "core/compatibility" ], + "heading": ".NET breaking changes" + }, + { + "names": [ "core", "fundamentals", "standard" ], + "heading": ".NET fundamentals" + }, + { + "names": [ "iot" ], + "heading": ".NET IoT libraries" + }, + { + "names": [ "machine-learning" ], + "heading": "ML.NET" + }, + { + "names": [ "spark" ], + "heading": ".NET for Apache Spark" + }, + { + "names": [ "visual-basic" ], + "heading": "Visual Basic language" + }, + { + "names": [ "orleans", "dotnet-orleans" ], + "heading": "Microsoft Orleans" + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 194f38b2d5252..48e744fe25b79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,4 +2,4 @@ Thank you for your interest in contributing to the .NET documentation! -We have moved our guidelines into a site-wide contribution guide. To see the guidance, visit the [Microsoft Docs contributor guide](https://docs.microsoft.com/contribute/dotnet/dotnet-contribute). +We have moved our guidelines into a site-wide contribution guide. To see the guidance, visit the [Microsoft Docs contributor guide](https://learn.microsoft.com/contribute/dotnet/dotnet-contribute). diff --git a/Directory.Build.props b/Directory.Build.props index 98f082d575e25..c1516106c183d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,5 +7,6 @@ true latest latest-recommended + true diff --git a/README.md b/README.md index f9c9ec90e626f..27c476ec15e55 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,47 @@ # .NET Docs -![Markdownlint](https://github.com/dotnet/docs/workflows/Markdownlint/badge.svg) [![target supported version](https://github.com/dotnet/docs/actions/workflows/version-sweep.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/version-sweep.yml) +[![GitHub contributors](https://img.shields.io/github/contributors/dotnet/docs.svg)](https://GitHub.com/dotnet/docs/graphs/contributors/) +[![GitHub repo size](https://img.shields.io/github/repo-size/dotnet/docs)](https://github.com/dotnet/docs) +[![GitHub issues-opened](https://img.shields.io/github/issues/dotnet/docs.svg)](https://GitHub.com/dotnet/docs/issues?q=is%3Aissue+is%3Aopened) +[![GitHub issues-closed](https://img.shields.io/github/issues-closed/dotnet/docs.svg)](https://GitHub.com/dotnet/docs/issues?q=is%3Aissue+is%3Aclosed) +[![GitHub pulls-opened](https://img.shields.io/github/issues-pr/dotnet/docs.svg)](https://GitHub.com/dotnet/docs/pulls?q=is%3Aissue+is%3Aopened) +[![GitHub pulls-merged](https://img.shields.io/github/issues-search/dotnet/docs?label=merged%20pull%20requests&query=is%3Apr%20is%3Aclosed%20is%3Amerged&color=darkviolet)](https://github.com/dotnet/docs/pulls?q=is%3Apr+is%3Aclosed+is%3Amerged) +[![GitHub pulls-unmerged](https://img.shields.io/github/issues-search/dotnet/docs?label=unmerged%20pull%20requests&query=is%3Apr%20is%3Aclosed%20is%3Aunmerged&color=red)](https://github.com/dotnet/docs/pulls?q=is%3Apr+is%3Aclosed+is%3Aunmerged) -This repository contains the conceptual documentation for .NET. The [.NET documentation site](https://docs.microsoft.com/dotnet) is built from multiple repositories in addition to this one: +This repository contains the conceptual documentation for .NET. The [.NET documentation site](https://learn.microsoft.com/dotnet) is built from multiple repositories in addition to this one: - [API reference](https://github.com/dotnet/dotnet-api-docs) - [.NET Compiler Platform SDK reference](https://github.com/dotnet/roslyn-api-docs) -Issues and tasks for all but the API reference repository are tracked here. We have a large community using these resources. We make our best effort to respond to issues in a timely fashion. You can read more about our procedures for classifying and resolving issues in our [Issues policy](issues-policy.md) topic. +Issues and tasks for all but the API reference repository are tracked here. We have a large community using these resources. We make our best effort to respond to issues in a timely fashion. You can read more about our procedures for classifying and resolving issues in our [Issues policy](issues-policy.md) topic. To create a new issue, [choose from any of the available templates](https://github.com/dotnet/docs/issues/new/choose). + +## :purple_heart: Contribute We welcome contributions to help us improve and complete the .NET docs. This is a very large repo, covering a large area. If this is your first visit, see our [labels and projects roadmap](styleguide/labels-projects.md) for help navigating the issues and projects in this repository. To contribute, see: -- The [Contributing Guide](CONTRIBUTING.md) for instructions on procedures we use. -- Issues labeled [`up-for-grabs`](https://github.com/dotnet/docs/issues?q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs) for ideas. -- [#Hacktoberfest and Microsoft Docs](https://docs.microsoft.com/contribute/hacktoberfest) for details on our participation in the annual event. +- The [.NET Contributor Guide :ledger:](https://learn.microsoft.com/contribute/dotnet/dotnet-contribute) for instructions on procedures we use. +- Issues labeled [`help wanted` :label:](https://github.com/dotnet/docs/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+) for ideas. +- [#Hacktoberfest and Microsoft Docs :jack_o_lantern:](https://learn.microsoft.com/contribute/hacktoberfest) for details on our participation in the annual event. -If you're interested in helping migrate existing code that targets the .NET Framework from the [retired Code Gallery](https://docs.microsoft.com/teamblog/msdn-code-gallery-retired) site to .NET Core applications stored in our [samples repository](https://github.com/dotnet/samples) and downloadable from the [Samples Browser](https://docs.microsoft.com/samples/browse), see the [Code Gallery migration](https://github.com/dotnet/docs/projects/88) project. The code gallery samples were moved to the [Microsoft Archive](https://github.com/microsoftarchive?q=msdn-code-gallery) organization. +## :bookmark_tabs: Code of conduct This project has adopted the code of conduct defined by the Contributor Covenant -to clarify expected behavior in our community. -For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). +to clarify expected behavior in our community. For more information, see the [.NET Foundation: Code of Conduct](https://dotnetfoundation.org/code-of-conduct). + +## :octocat: GitHub Action workflows + +- [![Live branch protection](https://github.com/dotnet/docs/actions/workflows/live-protection.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/live-protection.yml): Adds a comment to PRs that were not automated, but rather manually created that target the `live` branch. +- [![Close stale issues](https://github.com/dotnet/docs/actions/workflows/stale.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/stale.yml): Closes stale issues that have not been updated in 180 days. +- [![`dependabot` auto-approve and auto-merge](https://github.com/dotnet/docs/actions/workflows/dependabot-approve-and-automerge.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/dependabot-approve-and-automerge.yml): Automatically approves and auto-merges PRs originating from the `dependabbot[bot]`. +- [![Generate what's new article](https://github.com/dotnet/docs/actions/workflows/whats-new.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/whats-new.yml): Creates a PR to generate the "What's new" article on the first of every month. +- [![Markdownlint](https://github.com/dotnet/docs/actions/workflows/markdownlint.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/markdownlint.yml): The current status for the entire repositories Markdown linter status. +- [![MSDocs build verifier](https://github.com/dotnet/docs/actions/workflows/docs-verifier.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/docs-verifier.yml): Runs various Markdown verifications, beyond the linter, such as ensuring links and redirects are valid. +- [![No response](https://github.com/dotnet/docs/actions/workflows/no-response.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/no-response.yml): If an issue is labeled with `needs-more-info` and the op doesn't respond within 14 days, the issue is closed. +- [![OPS status checker](https://github.com/dotnet/docs/actions/workflows/check-for-build-warnings.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/check-for-build-warnings.yml): Builds the site for the PR in context, and verifies the build reporting either, `success,` `warnings`, or `error`. +- [![Snippets 5000](https://github.com/dotnet/docs/actions/workflows/build-validation.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/build-validation.yml): Custom .NET build validation, locates code impacted by a PR, and builds. +- [![Target supported version](https://github.com/dotnet/docs/actions/workflows/version-sweep.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/version-sweep.yml): Runs monthly, creating issues on projects that target .NET versions that are out of support. +- [![Update dependabot.yml](https://github.com/dotnet/docs/actions/workflows/dependabot-bot.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/dependabot-bot.yml): Automatically updates the `dependabot` configuration weekly, but only if required. +- [![quest import](https://github.com/dotnet/docs/actions/workflows/quest.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/quest.yml): Automatically synchronizes issues with Quest (Azure DevOps). +- [![bulk quest import](https://github.com/dotnet/docs/actions/workflows/quest-bulk.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/quest-bulk.yml): Manual bulk import of issues into Quest (Azure DevOps). diff --git a/admin/governance.md b/admin/governance.md new file mode 100644 index 0000000000000..3611b621b4bfe --- /dev/null +++ b/admin/governance.md @@ -0,0 +1,64 @@ +# :octocat: Repository governance + +This file contains notes on our tools and processes for working in this repo. It's meant as notes for the maintainers, but may be informative for other contributors as well. + +## :gear: Workflows installed + +We have the following workflows that run on most PRs: + +- **OpenPublishing.Build** *required*: This builds published docs site using the changes from the PR. It's how we verify changes before publishing. We can see any changes in a staging site before publishing. +- **Snippets 5000 / snippets-build** *required*: This workflow scans the PR for any changes to source code. If any source code files are changed, it ensures the changes are part of a project, and then builds that project. We require all code snippets to be included in a project that produces a clean build on the current version of .NET (unless a config file pins that project to a known older version.) +- **MSDocs build verifier / MSDocs build verifier** *required*: This action verifies links and other docfx syntax for our build. +- **OPS status checker / Look for build warnings** *required*: This action fails when the OPS build has warnings. That prevents auto-merge from merging PRs with warnings. +- **license/cla**: This ensures that any contributor making large scale changes to our docs has signed the Contributor License Agreement (CLA). +- **.github/workflows/live-protection.yml**: This action ensures that the only PRs that target the `live` branch use the `main` branch as the source. All work should take place against the `main` branch. Nothing should be merged to `live` that hasn't been published on the staging site in the `main` branch. +- **Markdownlint / lint**: We like our markdown clean and spiffy. Please keep it that way. +- **dependabot auto-approve and auto-merge**: We have dependabot installed to open PRs when referenced NuGet packages are updated. If the build is clean for those updates, those PRs are automatically merged by this action. + +## :shield: Branch protection rules + +We've also configured a variety of branch protection rules in place on the `main` branch: + +- All changes to the `main` branch must be through a PR (no direct push privileges to `main`) +- All PRs must have at least one approval from a maintainer. +- All required checks must pass. +- All open conversations must be resolved. +- Protection rules apply to everyone, including administrators. + +> On rare occasions, we may override the required checks. This should be rare, and only after exhausting other options. +> +> If you do need to override the required settings, turn off the "Do not allow bypassing the above settings*. Then, refresh the PR, merge it, and the reapply the "Do not allow bypassing" setting. This is only available to administrators. + +If you change any branch protection rules, alert the @dotnet/docs team via email about the change. + +## :white_check_mark: Automation we use + +We have several other workflows installed to automate a variety of tasks: + +- **dependabot-bot**: This regenerates our dependabot config file periodically. +- **no-response**: This action automatically closes PRs or issues where we've asked for clarification from the original poster (OP) and haven't heard any response. It will only happen when the `needs-more-info` label has been applied. Use that label with care. +- **quest**: This action links and synchronizes GitHub issues with an internal Azure DevOps instance used for reporting and planning. +- **version-sweep**: This action periodically checks for samples that use TFMs that are out of support. It helps us keep our samples up to date. +- **whats-new**: This action runs once a month to publish our "What's new" article. +- **repo-man**: This GitHub workflow examines issues and PRs and adds classification labels. + +You can see the full details on configured GitHub actions in our [README.MD](https://github.com/dotnet/docs#octocat-github-action-workflows) file. + +## :label: Labels for automation + +- `needs-more-info`: We apply this label when we can't proceed without more information. An action will close this issue if there isn't a response. It will remove the label when a new comment from the OP is added. +- `rerun-action-*`: These labels trigger *repo-man* to apply classification labels. +- `:world_map: reQUEST`, `:pushpin: seQUESTered`: These labels trigger the Quest action to sync issues with our internal Azure DevOps board. + +## :ledger: Labels for reporting + +- `*/tech`, `*/prod`: These labels provide 1st and 2nd level organization to our content. +- `doc-*`, `product-*`: These provide classifications for what kind of issue is it. +- `okr-*`: These map to our internal OKR system. + +## :heavy_plus_sign: Additional fields in projects + +We're assigning values to the following additional properties using the new GitHub project system. + +- *Size*: The relative size of work required. +- *Priority*: The relative priority of this issue. This should map to the `Pri?` labels. diff --git a/api/index.md b/api/index.md index 95248f3f38445..63da4aa77be00 100644 --- a/api/index.md +++ b/api/index.md @@ -3,15 +3,15 @@ description: "Learn more about: .NET API browser" layout: ApiBrowserPage hide_bc: true title: .NET API browser -quickFilterColumn1: net-6.0,netframework-4.8,netstandard-2.1 -quickFilterColumn2: aspnetcore-6.0,efcore-6.0,xamarin-forms +quickFilterColumn1: net-7.0,netframework-4.8,netstandard-2.1 +quickFilterColumn2: aspnetcore-7.0,efcore-7.0,net-maui-7.0 quickFilterColumn3: azure-dotnet,ml-dotnet,spark-dotnet ms.topic: landing-page ms.custom: "updateeachrelease" -ms.date: 06/22/2022 +ms.date: 11/18/2022 --- # .NET API browser Welcome to the .NET API browser – your one-stop shop for all .NET-based APIs from Microsoft. Start searching for any managed APIs by typing in the box below. -You can learn more about the API browser [in our blog post](/teamblog/announcing-unified-dotnet-experience-on-docs). If you have any feedback, create a new issue in the [MicrosoftDocs/feedback repository on GitHub](https://github.com/MicrosoftDocs/feedback/issues). +If you have any feedback, create a new issue in the [dotnet/dotnet-api-docs](https://github.com/dotnet/dotnet-api-docs/issues) repo. diff --git a/docfx.json b/docfx.json index a09c276070d05..6e03b1b3fcd81 100644 --- a/docfx.json +++ b/docfx.json @@ -1,6 +1,15 @@ { "build": { - "markdownEngineName": "markdig", + "markdownEngineName": "markdig", + "markdownEngineProperties": { + "markdigExtensions": [ + "abbreviations", + "definitionlists", + "tasklists", + "footnotes", + "diagrams" + ] + }, "content": [ { "files": ["api/**/*.md"] @@ -30,7 +39,8 @@ "csharp-7.3/*.md", "csharp-8.0/*.md", "csharp-9.0/*.md", - "csharp-10.0/*.md" + "csharp-10.0/*.md", + "csharp-11.0/*.md" ], "src": "_csharplang/proposals", "dest": "csharp/language-reference/proposals", @@ -42,13 +52,17 @@ "csharp-7.0/binary-literals.md", "csharp-7.0/digit-separators.md", "csharp-7.0/expression-bodied-everything.md", + "csharp-7.0/local-functions.md", "csharp-7.0/ref-locals-returns.md", + "csharp-7.0/throw-expression.md", "csharp-7.0/tuples.md", "csharp-7.0/value-task.md", + "csharp-7.1/async-main.md", "csharp-7.2/leading-separator.md", "csharp-7.2/readonly-struct.md", "csharp-7.2/ref-extension-methods.md", "csharp-7.2/ref-struct-and-span.md", + "csharp-7.2/private-protected.md", "csharp-7.3/enum-delegate-constraints.md", "csharp-7.3/ref-loops.md", "csharp-8.0/alternative-interpolated-verbatim.md", @@ -99,7 +113,10 @@ "rules": { "docs-link-absolute": { "exclude": [ + "_csharplang/proposals/csharp-7.0/local-functions.md", + "_csharplang/proposals/csharp-7.0/throw-expression.md", "_csharplang/proposals/csharp-7.2/private-protected.md", + "_csharplang/proposals/csharp-7.2/readonly-struct.md", "_csharplang/proposals/csharp-7.3/blittable.md" ] } @@ -181,7 +198,7 @@ "docs/architecture/**/**.md": "dotnet-architecture", "docs/azure/**/*.*": "dotnet-azure", "docs/core/**/*.*": "dotnet-fundamentals", - "docs/core/install/**/**.md": "dotnet", + "docs/core/install/**/**.md": ".net", "docs/csharp/**/*.*": "dotnet-csharp", "docs/framework/**/**.md": "dotnet-framework", "docs/fsharp/**/**.md": "dotnet-fsharp", @@ -287,6 +304,7 @@ "docs/core/whats-new/**/**.md": "adegeo", "docs/csharp/**/*.*": "billwagner", "docs/framework/**/**.md": "gewarren", + "docs/framework/additional-apis/pos-for-net/**/**.md": "TerryWarwick", "docs/framework/app-domains/**/**.md": "gewarren", "docs/framework/configure-apps/file-schema/network/**/**.md": "karelz", "docs/framework/configure-apps/file-schema/wcf/**/**.md": "HongGit", @@ -449,7 +467,8 @@ "_csharplang/proposals/csharp-8.0/*.md": "09/10/2019", "_csharplang/proposals/csharp-9.0/*.md": "07/29/2020", "_csharplang/proposals/csharp-10.0/*.md": "08/07/2021", - "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "03/21/2022", + "_csharplang/proposals/csharp-11.0/*.md": "09/30/2022", + "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "09/30/2022", "_vblang/spec/*.md": "07/21/2017" }, "ms.technology": { @@ -499,7 +518,7 @@ "docs/csharp/language-reference/compiler-messages/**/**.md": "csharp-diagnostics", "docs/csharp/roslyn-sdk/**/**.md": "csharp-roslyn", "docs/csharp/tour-of-csharp/**/**.md": "csharp-get-started", - "docs/core/install/**/**.md": "dotnet-install", + "docs/core/install/**/**.md": "dotnet-installation", "docs/core/tools/**/**.md": "dotnet-cli", "docs/core/docker/**/**.md": "dotnet-docker", "docs/framework/configure-apps/file-schema/network/**/**.md": "dotnet-networking", @@ -590,14 +609,11 @@ "_csharpstandard/standard/Bibliography.md": "Bibliography", "_csharplang/proposals/csharp-7.0/pattern-matching.md": "Pattern matching", - "_csharplang/proposals/csharp-7.0/local-functions.md": "Local functions", "_csharplang/proposals/csharp-7.0/out-var.md": "Out variable declarations", - "_csharplang/proposals/csharp-7.0/throw-expression.md": "Throw expressions", "_csharplang/proposals/csharp-7.0/binary-literals.md": "Binary literals", "_csharplang/proposals/csharp-7.0/digit-separators.md": "Digit separators", "_csharplang/proposals/csharp-7.0/task-types.md": "Async task types", - "_csharplang/proposals/csharp-7.1/async-main.md": "Async main method", "_csharplang/proposals/csharp-7.1/target-typed-default.md": "Default expressions", "_csharplang/proposals/csharp-7.1/infer-tuple-names.md": "Infer tuple member names", "_csharplang/proposals/csharp-7.1/generics-pattern-match.md": "Pattern matching with generics", @@ -660,10 +676,26 @@ "_csharplang/proposals/csharp-10.0/lambda-attributes.md": "Lambda attributes", "_csharplang/proposals/csharp-10.0/caller-argument-expression.md": "Caller argument expression", "_csharplang/proposals/csharp-10.0/enhanced-line-directives.md": "Enhanced #line directives", - "_csharplang/proposals/csharp-10.0/generic-attributes.md": "Generic attributes", "_csharplang/proposals/csharp-10.0/improved-definite-assignment.md": "Improved definite assignment analysis", "_csharplang/proposals/csharp-10.0/async-method-builders.md": "AsyncMethodBuilder override", + "_csharplang/proposals/csharp-11.0/generic-attributes.md": "Generic attributes", + "_csharplang/proposals/csharp-11.0/auto-default-structs.md": "Auto-default struct", + "_csharplang/proposals/csharp-11.0/checked-user-defined-operators.md": "Checked user defined operators", + "_csharplang/proposals/csharp-11.0/extended-nameof-scope.md": "Extended nameof parameter scope", + "_csharplang/proposals/csharp-11.0/file-local-types.md": "File local types", + "_csharplang/proposals/csharp-11.0/list-patterns.md": "List patterns", + "_csharplang/proposals/csharp-11.0/low-level-struct-improvements.md": "Low level struct improvements", + "_csharplang/proposals/csharp-11.0/new-line-in-interpolation.md": "Interpolated string expression newline", + "_csharplang/proposals/csharp-11.0/numeric-intptr.md": "Numeric IntPtr", + "_csharplang/proposals/csharp-11.0/pattern-match-span-of-char-on-string.md": "Pattern match Span", + "_csharplang/proposals/csharp-11.0/raw-string-literal.md": "Raw string literal", + "_csharplang/proposals/csharp-11.0/relaxing_shift_operator_requirements.md": "Relaxed right shift requirement", + "_csharplang/proposals/csharp-11.0/required-members.md": "Required members", + "_csharplang/proposals/csharp-11.0/static-abstracts-in-interfaces.md": "Static abstract methods in interfaces", + "_csharplang/proposals/csharp-11.0/unsigned-right-shift-operator.md": "Unsigned right shift operator", + "_csharplang/proposals/csharp-11.0/utf8-string-literals.md": "UTF-8 string literals", + "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "C# compiler breaking changes since C# 10", "_vblang/spec/introduction.md": "Introduction", @@ -714,14 +746,11 @@ "_csharpstandard/standard/Bibliography.md": "This appendix lists external standards referenced in this specification.", "_csharplang/proposals/csharp-7.0/pattern-matching.md": "This feature specification describes the pattern matching featues added in C# 7.0. Note that future releases built on this initial set of features.", - "_csharplang/proposals/csharp-7.0/local-functions.md": "This feature specification describes local functions, which are functions that are embedded in another method or function.", "_csharplang/proposals/csharp-7.0/out-var.md": "This feature specification describes syntax enhancements that enable declaring 'out' variables in the expression where they are used.", - "_csharplang/proposals/csharp-7.0/throw-expression.md": "This feature specification describes syntax enhancements that allow 'throw' to be either an expression or a statement.", "_csharplang/proposals/csharp-7.0/binary-literals.md": "This feature specification describes the syntax to declare binary literals for integral constants.", "_csharplang/proposals/csharp-7.0/digit-separators.md": "This feature specification describes the syntax to allow separators between groups of digits in numeric literals.", "_csharplang/proposals/csharp-7.0/task-types.md": "This feature specification describes the syntax to support async return types that match a pattern, rather than restricting them to Task.", - "_csharplang/proposals/csharp-7.1/async-main.md": "This feature specification describes syntax enhancements to declare a Main method that is async and returns a Task type.", "_csharplang/proposals/csharp-7.1/target-typed-default.md": "This feature specification describes the syntax to use the default keyword where the type of the expression can be inferred by the compiler.", "_csharplang/proposals/csharp-7.1/infer-tuple-names.md": "This feature specification describes how the compiler interprets and infers the members names in a tuple.", "_csharplang/proposals/csharp-7.1/generics-pattern-match.md": "This feature specification describes syntax enhancements to enable pattern matching with generic types.", @@ -784,10 +813,26 @@ "_csharplang/proposals/csharp-10.0/lambda-attributes.md": "This feature specification describes the syntax enhancements to declare attributes on lambda expressions.", "_csharplang/proposals/csharp-10.0/caller-argument-expression.md": "This feature specification describes attributes that can be used to enable the compiler to convert a caller argument expression into text. This is primarily used for debugging and testing scenarios.", "_csharplang/proposals/csharp-10.0/enhanced-line-directives.md": "This feature specification describes updates to the #line directive. These enhancements support scenarios like Razor where the Razor engine wants more control over the line number so that it maps to your source rather than the generated file.", - "_csharplang/proposals/csharp-10.0/generic-attributes.md": "This feature specification describes generic attributes, where a generic class may derive from 'System.Attribute'.", "_csharplang/proposals/csharp-10.0/improved-definite-assignment.md": "This feature specification describes updates to the rules for definite assignment analysis. The result is more accurate warnings for when a variable may not be definitely assigned, or may not be null.", "_csharplang/proposals/csharp-10.0/async-method-builders.md": "This feature specification describes new rules to enable types to override the default AsyncMethodBuilder. This will be used by the runtime for performance improvements.", + "_csharplang/proposals/csharp-11.0/generic-attributes.md": "This feature specification describes generic attributes, where a generic class may derive from 'System.Attribute'.", + "_csharplang/proposals/csharp-11.0/auto-default-structs.md": "This feature updates the rules for struct initialization and default values. This feature standardizes the behavior for default struct values and newly initialized structs.", + "_csharplang/proposals/csharp-11.0/checked-user-defined-operators.md": "This feature enables checked and unchecked alternatives for some operators.", + "_csharplang/proposals/csharp-11.0/extended-nameof-scope.md": "This feature enables the nameof expression to be used with parameter names in a method declaration.", + "_csharplang/proposals/csharp-11.0/file-local-types.md": "This feature allows you to create types (either structs or classes) that are visibile only in the file in which they are declared. This is primarily useful for source generators.", + "_csharplang/proposals/csharp-11.0/list-patterns.md": "This feature describes enhancements to pattern matching to support patterns in lists of elements.", + "_csharplang/proposals/csharp-11.0/low-level-struct-improvements.md": "This feature specification describes several features that improve the performance of `struct` types: `ref` fields and overriding lifetime defaults.", + "_csharplang/proposals/csharp-11.0/new-line-in-interpolation.md": "This feature describes changes to allow newlines in the interpolation expressions in an interpolated string expression.", + "_csharplang/proposals/csharp-11.0/numeric-intptr.md": "This feature enables the IntPtr and UIntPtr to be treated as the numeric types nint and nuint, respectively.", + "_csharplang/proposals/csharp-11.0/pattern-match-span-of-char-on-string.md": "This feature enables a Span to pattern match a literal string value.", + "_csharplang/proposals/csharp-11.0/raw-string-literal.md": "This feature describes raw string literals. Raw string literals enable string literals to avoid almost all escape sequences.", + "_csharplang/proposals/csharp-11.0/relaxing_shift_operator_requirements.md": "This feature removes the restriction that the right operand of a right shift must be an integer.", + "_csharplang/proposals/csharp-11.0/required-members.md": "This feature defines the required modifier. The required modifier instructs the compiler that a field or property must be initialized during the construction of a new object.", + "_csharplang/proposals/csharp-11.0/static-abstracts-in-interfaces.md": "This feature enables an interface to define static members. This enables interfaces to define operators that must be provided by implementing types.", + "_csharplang/proposals/csharp-11.0/unsigned-right-shift-operator.md": "This feature defines a logical right-shift operator, `>>>`. The logical right shift operator always shifts in 0 values in the left-most bits during a shift.", + "_csharplang/proposals/csharp-11.0/utf8-string-literals.md": "This feature enables the `u8` suffix on a string literal. The `u8` suffix instructs the compiler to convert the UTF-8 string literal to a `ReadOnlySpan`.", + "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "Learn about any breaking changes since the initial release of C# 10", "_vblang/spec/introduction.md": "This chapter provides and introduction to the Visual Basic language.", @@ -813,6 +858,7 @@ "_csharplang/proposals/csharp-8.0/*.md": "C# 8.0 draft specifications", "_csharplang/proposals/csharp-9.0/*.md": "C# 9.0 draft specifications", "_csharplang/proposals/csharp-10.0/*.md": "C# 10.0 draft specifications", + "_csharplang/proposals/csharp-11.0/*.md": "C# 11.0 draft specifications", "docs/framework/**/**.md": ".NET Framework", "docs/framework/data/adonet/**/**.md": "ADO.NET", "docs/framework/wcf/**/**.md": "WCF", diff --git a/docs/architecture/blazor-for-web-forms-developers/index.md b/docs/architecture/blazor-for-web-forms-developers/index.md index 6cea5c244e5c7..46b33e6008537 100644 --- a/docs/architecture/blazor-for-web-forms-developers/index.md +++ b/docs/architecture/blazor-for-web-forms-developers/index.md @@ -76,9 +76,7 @@ This book is an introduction to Blazor, not a comprehensive migration guide. Whi You can find the official Blazor home page and documentation at . -## Send your feedback - -This book and related samples are constantly evolving, so your feedback is welcomed! If you have comments about how this book can be improved, use the feedback section at the bottom of any page built on [GitHub issues](https://github.com/dotnet/docs/issues). +[!INCLUDE [feedback](../includes/feedback.md)] >[!div class="step-by-step"] >[Next](introduction.md) diff --git a/docs/architecture/cloud-native/index.md b/docs/architecture/cloud-native/index.md index 582d4763f68b1..ebda59bd6a8e2 100644 --- a/docs/architecture/cloud-native/index.md +++ b/docs/architecture/cloud-native/index.md @@ -87,9 +87,7 @@ This guide begins by defining cloud native and introducing a reference applicati This guide is available both in [PDF](https://dotnet.microsoft.com/download/e-book/cloud-native-azure/pdf) form and online. Feel free to forward this document or links to its online version to your team to help ensure common understanding of these topics. Most of these topics benefit from a consistent understanding of the underlying principles and patterns, as well as the trade-offs involved in decisions related to these topics. Our goal with this document is to equip teams and their leaders with the information they need to make well-informed decisions for their applications' architecture, development, and hosting. -## Send your feedback - -This book and related samples are constantly evolving, so your feedback is welcomed! If you have comments about how this book can be improved, use the feedback section at the bottom of any page built on [GitHub issues](https://github.com/dotnet/docs/issues). +[!INCLUDE [feedback](../includes/feedback.md)] >[!div class="step-by-step"] >[Next](introduction.md) diff --git a/docs/architecture/cloud-native/logging-with-elastic-stack.md b/docs/architecture/cloud-native/logging-with-elastic-stack.md index a40e0a8a41e2b..4ba26339c7d74 100644 --- a/docs/architecture/cloud-native/logging-with-elastic-stack.md +++ b/docs/architecture/cloud-native/logging-with-elastic-stack.md @@ -14,7 +14,7 @@ Collectively these tools are known as the Elastic Stack or ELK stack. ## Elastic Stack -The Elastic Stack is a powerful option for gathering information from a Kubernetes cluster. Kubernetes supports sending logs to an Elasticsearch endpoint, and for the [most part](https://v1-19.docs.kubernetes.io/docs/tasks/debug-application-cluster/logging-elasticsearch-kibana/), all you need to get started is to set the environment variables as shown in Figure 7-5: +The Elastic Stack is a powerful option for gathering information from a Kubernetes cluster. Kubernetes supports sending logs to an Elasticsearch endpoint, and for the [most part](https://www.elastic.co/guide/en/kibana/master/logging-configuration.html), all you need to get started is to set the environment variables as shown in Figure 7-5: ```kubernetes KUBE_LOGGING_DESTINATION=elasticsearch @@ -34,7 +34,7 @@ Elastic Stack provides centralized logging in a low-cost, scalable, cloud-friend ## Logstash -The first component is [Logstash](https://www.elastic.co/products/logstash). This tool is used to gather log information from a large variety of different sources. For instance, Logstash can read logs from disk and also receive messages from logging libraries like [Serilog](https://serilog.net/). Logstash can do some basic filtering and expansion on the logs as they arrive. For instance, if your logs contain IP addresses then Logstash may be configured to do a geographical lookup and obtain a country or even city of origin for that message. +The first component is [Logstash](https://www.elastic.co/products/logstash). This tool is used to gather log information from a large variety of different sources. For instance, Logstash can read logs from disk and also receive messages from logging libraries like [Serilog](https://serilog.net/). Logstash can do some basic filtering and expansion on the logs as they arrive. For instance, if your logs contain IP addresses then Logstash may be configured to do a geographical lookup and obtain a country/region or even city of origin for that message. Serilog is a logging library for .NET languages, which allows for parameterized logging. Instead of generating a textual log message that embeds fields, parameters are kept separate. This library allows for more intelligent filtering and searching. A sample Serilog configuration for writing to Logstash appears in Figure 7-7. diff --git a/docs/architecture/cloud-native/security.md b/docs/architecture/cloud-native/security.md index 345e64f01a3cd..1f90167f1e955 100644 --- a/docs/architecture/cloud-native/security.md +++ b/docs/architecture/cloud-native/security.md @@ -8,7 +8,7 @@ ms.date: 04/06/2022 [!INCLUDE [download-alert](includes/download-alert.md)] -Not a day goes by where the news doesn't contain some story about a company being hacked or somehow losing their customers' data. Even countries aren't immune to the problems created by treating security as an afterthought. For years, companies have treated the security of customer data and, in fact, their entire networks as something of a "nice to have". Windows servers were left unpatched, ancient versions of PHP kept running, and MongoDB databases left wide open to the world. +Not a day goes by where the news doesn't contain some story about a company being hacked or somehow losing their customers' data. Even countries/regions aren't immune to the problems created by treating security as an afterthought. For years, companies have treated the security of customer data and, in fact, their entire networks as something of a "nice to have". Windows servers were left unpatched, ancient versions of PHP kept running, and MongoDB databases left wide open to the world. However, there are starting to be real-world consequences for not maintaining a security mindset when building and deploying applications. Many companies learned the hard way what can happen when servers and desktops aren't patched during the 2017 outbreak of [NotPetya](https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/). The cost of these attacks has easily reached into the billions, with some estimates putting the losses from this single attack at 10 billion US dollars. diff --git a/docs/architecture/cloud-native/service-to-service-communication.md b/docs/architecture/cloud-native/service-to-service-communication.md index 11fe111e08767..42367cb1b496e 100644 --- a/docs/architecture/cloud-native/service-to-service-communication.md +++ b/docs/architecture/cloud-native/service-to-service-communication.md @@ -143,7 +143,7 @@ In the previous figure, note the point-to-point relationship. Two instances of t Message queuing is an effective way to implement communication where a producer can asynchronously send a consumer a message. However, what happens when *many different consumers* are interested in the same message? A dedicated message queue for each consumer wouldn't scale well and would become difficult to manage. -To address this scenario, we move to the third type of message interaction, the *event*. One microservice announces that an action had occurred. Other microservices, if interested, react to the action, or event. +To address this scenario, we move to the third type of message interaction, the *event*. One microservice announces that an action had occurred. Other microservices, if interested, react to the action, or event. This is also known as the [event-driven architectural style](/azure/architecture/guide/architecture-styles/event-driven). Eventing is a two-step process. For a given state change, a microservice publishes an event to a message broker, making it available to any other interested microservice. The interested microservice is notified by subscribing to the event in the message broker. You use the [Publish/Subscribe](/azure/architecture/patterns/publisher-subscriber) pattern to implement [event-based communication](/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/integration-event-based-microservice-communications). diff --git a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/build-aspnet-core-applications-linux-containers-aks-kubernetes.md b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/build-aspnet-core-applications-linux-containers-aks-kubernetes.md index eff303966551b..c846f5fea90e3 100644 --- a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/build-aspnet-core-applications-linux-containers-aks-kubernetes.md +++ b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/build-aspnet-core-applications-linux-containers-aks-kubernetes.md @@ -26,11 +26,13 @@ ASP.NET Core is a general-purpose development platform maintained by Microsoft a This example uses a couple of simple projects based on Visual Studio templates, so you don't need much additional knowledge to create the sample. You only have to create the project using a standard template that includes all the elements to run a small project with a REST API and a Web App with Razor pages, using ASP.NET Core 6.0 technology. +For reference, you can download the sample from .NET Application Architecture's repo [explore-docker](https://github.com/dotnet-architecture/explore-docker). + ![Add new project window in Visual Studio, selecting ASP.NET Core Web Application.](media/build-aspnet-core-applications-linux-containers-aks-kubernetes/create-aspnet-core-application.png) **Figure 4-35**. Creating an ASP.NET Core Web Application in Visual Studio 2022. -To create the sample project in Visual Studio, select **File** > **New** > **Project**, select the **Web** project type and then the **ASP.NET Core Web Application** template. You can also search for the template if you need it. +To create the sample project in Visual Studio, select **File** > **New** > **Project**, select the **Web** project type and then the **ASP.NET Core Web Api** template. You can also search for the template if you need it. Then enter the application name and location as shown in the next image. @@ -60,7 +62,7 @@ To complete adding Docker support, you can choose Windows or Linux. In this case With these simple steps, you have your ASP.NET Core 6.0 application running on a Linux container. -In a similar way, you can also add a very simple **WebApp** project (Figure 4-40) to consume the web API endpoint, although the details are not discussed here. +In a similar way, you can also add a very simple **WebApp** project (Figure 4-40) to consume the web API endpoint, although the details can be seen in the code [repo](https://github.com/dotnet-architecture/explore-docker). After that, you add orchestrator support for your **WebApi** project as shown next, in image 4-40. diff --git a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-development-environment.md b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-development-environment.md index e68d10eecd486..2eca841c006e2 100644 --- a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-development-environment.md +++ b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-development-environment.md @@ -26,7 +26,7 @@ It's recommended that you use Visual Studio 2022 or later with the built-in Dock ### Visual Studio for Mac (Mac development machine) -You can use [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/?utm_medium=microsoft&utm_source=docs.microsoft.com&utm_campaign=inline+link) when developing Docker-based applications. Visual Studio for Mac offers a richer IDE when compared to Visual Studio Code for Mac. +You can use [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/?utm_medium=microsoft&utm_source=learn.microsoft.com&utm_campaign=inline+link) when developing Docker-based applications. Visual Studio for Mac offers a richer IDE when compared to Visual Studio Code for Mac. ## Language and framework choices diff --git a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-inner-loop-workflow.md b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-inner-loop-workflow.md index 9b6c17afe8365..e322273c84501 100644 --- a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-inner-loop-workflow.md +++ b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/docker-apps-inner-loop-workflow.md @@ -145,7 +145,7 @@ In the DockerFile, you can also instruct Docker to listen to the TCP port that y You can specify additional configuration settings in the Dockerfile, depending on the language and framework you're using. For instance, the `ENTRYPOINT` line with `["dotnet", "WebMvcApplication.dll"]` tells Docker to run a .NET application. If you're using the SDK and the .NET CLI (`dotnet CLI`) to build and run the .NET application, this setting would be different. The key point here is that the ENTRYPOINT line and other settings depend on the language and platform you choose for your application. > [!TIP] -> For more information about building Docker images for .NET applications, go to [https://docs.microsoft.com/dotnet/core/docker/building-net-docker-images](/aspnet/core/host-and-deploy/docker/building-net-docker-images). +> For more information about building Docker images for .NET applications, see [https://learn.microsoft.com/dotnet/core/docker/build-container](../../../core/docker/build-container.md). > > To learn more about building your own images, go to . @@ -292,7 +292,7 @@ Visual Studio Code supports debugging Docker if you're using Node.js and other p You also can debug .NET or .NET Framework containers in Docker when using Visual Studio for Windows or Mac, as described in the next section. > [!TIP] -> To learn more about debugging Node.js Docker containers, see [https://docs.microsoft.com/archive/blogs/user_ed/visual-studio-code-new-features-13-big-debugging-updates-rich-object-hover-conditional-breakpoints-node-js-mono-more](/archive/blogs/user_ed/visual-studio-code-new-features-13-big-debugging-updates-rich-object-hover-conditional-breakpoints-node-js-mono-more). +> To learn more about debugging Node.js Docker containers, see [https://learn.microsoft.com/archive/blogs/user_ed/visual-studio-code-new-features-13-big-debugging-updates-rich-object-hover-conditional-breakpoints-node-js-mono-more](/archive/blogs/user_ed/visual-studio-code-new-features-13-big-debugging-updates-rich-object-hover-conditional-breakpoints-node-js-mono-more). > [!div class="step-by-step"] > [Previous](docker-apps-development-environment.md) diff --git a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/media/build-aspnet-core-applications-linux-containers-aks-kubernetes/enter-project-name-and-location.png b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/media/build-aspnet-core-applications-linux-containers-aks-kubernetes/enter-project-name-and-location.png index 2e283f60fdef1..835dc0eb089ba 100644 Binary files a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/media/build-aspnet-core-applications-linux-containers-aks-kubernetes/enter-project-name-and-location.png and b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/media/build-aspnet-core-applications-linux-containers-aks-kubernetes/enter-project-name-and-location.png differ diff --git a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/orchestrate-high-scalability-availability.md b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/orchestrate-high-scalability-availability.md index 067f7f66c0d19..9bc3e6196877c 100644 --- a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/orchestrate-high-scalability-availability.md +++ b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/orchestrate-high-scalability-availability.md @@ -84,7 +84,7 @@ For further implementation information on Helm charts and Kubernetes, see the se ## Additional resources - **Getting started with Azure Kubernetes Service (AKS)** \ - [https://docs.microsoft.com/azure/aks/kubernetes-walkthrough-portal](/azure/aks/kubernetes-walkthrough-portal) + [https://learn.microsoft.com/azure/aks/kubernetes-walkthrough-portal](/azure/aks/kubernetes-walkthrough-portal) - **Kubernetes.** The official site. \ diff --git a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/visual-studio-tools-for-docker.md b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/visual-studio-tools-for-docker.md index e76415ee6f9a6..1d2267ecb83b4 100644 --- a/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/visual-studio-tools-for-docker.md +++ b/docs/architecture/containerized-lifecycle/design-develop-containerized-apps/visual-studio-tools-for-docker.md @@ -65,10 +65,10 @@ For more detailed configurations refer to [Container Tools settings](/visualstud > [!TIP] > For further details on the services implementation and use of Visual Studio Tools for Docker, read the following articles: > -> Use the Containers tool window to view container details such as the filesystem, logs, environment, ports, and more: [https://docs.microsoft.com/visualstudio/containers/view-and-diagnose-containers](/visualstudio/containers/view-and-diagnose-containers) -> Debug apps in a local Docker container: [https://docs.microsoft.com/visualstudio/containers/edit-and-refresh](/visualstudio/containers/edit-and-refresh) +> Use the Containers tool window to view container details such as the filesystem, logs, environment, ports, and more: [https://learn.microsoft.com/visualstudio/containers/view-and-diagnose-containers](/visualstudio/containers/view-and-diagnose-containers) +> Debug apps in a local Docker container: [https://learn.microsoft.com/visualstudio/containers/edit-and-refresh](/visualstudio/containers/edit-and-refresh) > -> Deploy an ASP.NET container to a container registry using Visual Studio: [https://docs.microsoft.com/visualstudio/containers/hosting-web-apps-in-docker](/visualstudio/containers/hosting-web-apps-in-docker) +> Deploy an ASP.NET container to a container registry using Visual Studio: [https://learn.microsoft.com/visualstudio/containers/hosting-web-apps-in-docker](/visualstudio/containers/hosting-web-apps-in-docker) > [!div class="step-by-step"] > [Previous](docker-apps-inner-loop-workflow.md) diff --git a/docs/architecture/containerized-lifecycle/docker-devops-workflow/docker-application-outer-loop-devops-workflow.md b/docs/architecture/containerized-lifecycle/docker-devops-workflow/docker-application-outer-loop-devops-workflow.md index c34c5e86c36a1..c9d98957c9094 100644 --- a/docs/architecture/containerized-lifecycle/docker-devops-workflow/docker-application-outer-loop-devops-workflow.md +++ b/docs/architecture/containerized-lifecycle/docker-devops-workflow/docker-application-outer-loop-devops-workflow.md @@ -97,7 +97,7 @@ With these Visual Studio Team Services tasks, a build Linux-Docker Host/VM provi > > > - Building .NET Linux Docker images with Azure DevOps Services: \ -> [https://docs.microsoft.com/archive/blogs/stevelasker/building-net-core-linux-docker-images-with-visual-studio-team-services](/archive/blogs/stevelasker/building-net-core-linux-docker-images-with-visual-studio-team-services) +> [https://learn.microsoft.com/archive/blogs/stevelasker/building-net-core-linux-docker-images-with-visual-studio-team-services](/archive/blogs/stevelasker/building-net-core-linux-docker-images-with-visual-studio-team-services) > > - Building a Linux-based Visual Studio Team Service build machine with Docker support: \ > diff --git a/docs/architecture/containerized-lifecycle/index.md b/docs/architecture/containerized-lifecycle/index.md index 56f9369d7dfc5..e8a66245b6c72 100644 --- a/docs/architecture/containerized-lifecycle/index.md +++ b/docs/architecture/containerized-lifecycle/index.md @@ -15,9 +15,7 @@ This guide is a general overview for developing and deploying containerized ASP. For low-level, development-related details you can see the [.NET Microservices: Architecture for Containerized .NET Applications](../microservices/index.md) guide and it related reference application [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers). -## Send us your feedback! - -We wrote this guide to help you understand the architecture of containerized applications and microservices in .NET. The guide and related reference application will be evolving, so we welcome your feedback! If you have comments about how this guide can be improved, submit feedback at . +[!INCLUDE [feedback](../includes/feedback.md)] ## Credits diff --git a/docs/architecture/containerized-lifecycle/road-to-modern-applications-based-on-containers.md b/docs/architecture/containerized-lifecycle/road-to-modern-applications-based-on-containers.md index 2e0097fbc4a26..6a7493bb27deb 100644 --- a/docs/architecture/containerized-lifecycle/road-to-modern-applications-based-on-containers.md +++ b/docs/architecture/containerized-lifecycle/road-to-modern-applications-based-on-containers.md @@ -18,10 +18,10 @@ This book belongs to a Microsoft suite of guides that cover many of the needs an You can find additional Microsoft e-books related to Docker containers in the list below: - **.NET Microservices: Architecture for Containerized .NET Applications** \ - [https://docs.microsoft.com/dotnet/architecture/microservices/](../microservices/index.md) + [https://learn.microsoft.com/dotnet/architecture/microservices/](../microservices/index.md) - **Modernize existing .NET applications with Azure cloud and Windows Containers** \ - [https://docs.microsoft.com/dotnet/architecture/modernize-with-azure-containers/](../modernize-with-azure-containers/index.md) + [https://learn.microsoft.com/dotnet/architecture/modernize-with-azure-containers/](../modernize-with-azure-containers/index.md) >[!div class="step-by-step"] >[Previous](docker-containers-images-and-registries.md) diff --git a/docs/architecture/containerized-lifecycle/run-manage-monitor-docker-environments/monitor-containerized-application-services.md b/docs/architecture/containerized-lifecycle/run-manage-monitor-docker-environments/monitor-containerized-application-services.md index 45363d7868109..0f85eb8354a5d 100644 --- a/docs/architecture/containerized-lifecycle/run-manage-monitor-docker-environments/monitor-containerized-application-services.md +++ b/docs/architecture/containerized-lifecycle/run-manage-monitor-docker-environments/monitor-containerized-application-services.md @@ -16,16 +16,16 @@ It's critical for applications split into multiple containers and microservices ### Additional resources - **Overview of Azure Monitor** \ - [https://docs.microsoft.com/azure/azure-monitor/overview](/azure/azure-monitor/overview) + [https://learn.microsoft.com/azure/azure-monitor/overview](/azure/azure-monitor/overview) - **What is Application Insights?** \ - [https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview](/azure/azure-monitor/app/app-insights-overview) + [https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview](/azure/azure-monitor/app/app-insights-overview) - **What is Azure Monitor Metrics?** \ - [https://docs.microsoft.com/azure/azure-monitor/platform/data-platform-metrics](/azure/azure-monitor/platform/data-platform-metrics) + [https://learn.microsoft.com/azure/azure-monitor/platform/data-platform-metrics](/azure/azure-monitor/platform/data-platform-metrics) - **Container Monitoring solution in Azure Monitor** \ - [https://docs.microsoft.com/azure/azure-monitor/insights/containers](/azure/azure-monitor/insights/containers) + [https://learn.microsoft.com/azure/azure-monitor/insights/containers](/azure/azure-monitor/insights/containers) ## Security and backup services diff --git a/docs/architecture/dapr-for-net-developers/dapr-at-20000-feet.md b/docs/architecture/dapr-for-net-developers/dapr-at-20000-feet.md index b66cc0aea84e8..960b8a68d1f64 100644 --- a/docs/architecture/dapr-for-net-developers/dapr-at-20000-feet.md +++ b/docs/architecture/dapr-for-net-developers/dapr-at-20000-feet.md @@ -64,7 +64,7 @@ The following table describes the infrastructure services provided by each block | [Bindings](bindings.md) | Trigger code from events raised by external resources with bi-directional communication. | | [Observability](observability.md) | Monitor and measure message calls across networked services. | | [Secrets](secrets-management.md) | Securely access external secret stores. | -| Actors | Encapsulate logic and data in reusable actor objects. | +| [Actors](actors.md) | Encapsulate logic and data in reusable actor objects. | Building blocks abstract the implementation of distributed application capabilities from your services. Figure 2-3 shows this interaction. diff --git a/docs/architecture/dapr-for-net-developers/getting-started.md b/docs/architecture/dapr-for-net-developers/getting-started.md index 0747942ad1c49..0a0eae4443730 100644 --- a/docs/architecture/dapr-for-net-developers/getting-started.md +++ b/docs/architecture/dapr-for-net-developers/getting-started.md @@ -308,7 +308,7 @@ Now, you'll configure communication between the services using Dapr [service inv

Welcome

-

Learn about building Web apps with ASP.NET Core.

+

Learn about building Web apps with ASP.NET Core.

@foreach (var forecast in (IEnumerable)ViewData["WeatherForecastData"]!) {

The forecast for @forecast.Date is @forecast.Summary!

diff --git a/docs/architecture/dapr-for-net-developers/index.md b/docs/architecture/dapr-for-net-developers/index.md index bfc79e2310bc4..2369a84cb07ef 100644 --- a/docs/architecture/dapr-for-net-developers/index.md +++ b/docs/architecture/dapr-for-net-developers/index.md @@ -77,9 +77,7 @@ A secondary audience is technical decision-makers who plan to choose whether to This guide is available both in [PDF](https://aka.ms/dapr-ebook) form and online. Feel free to forward this document or links to its online version to your team to help ensure common understanding of these topics. Most of these topics benefit from a consistent understanding of the underlying principles and patterns, as well as the trade-offs involved in decisions related to these topics. Our goal with this document is to equip teams and their leaders with the information they need to make well-informed decisions for their applications' architecture, development, and hosting. -## Send your feedback - -This book and related samples are constantly evolving, so your feedback is welcomed! If you have comments about how this book can be improved, use the feedback section at the bottom of any page built on [GitHub issues](https://github.com/dotnet/docs/issues). +[!INCLUDE [feedback](../includes/feedback.md)] >[!div class="step-by-step"] >[Next](foreword.md) diff --git a/docs/architecture/dapr-for-net-developers/observability.md b/docs/architecture/dapr-for-net-developers/observability.md index ff177b7b136c4..e9908a59bb5c3 100644 --- a/docs/architecture/dapr-for-net-developers/observability.md +++ b/docs/architecture/dapr-for-net-developers/observability.md @@ -1,6 +1,6 @@ --- title: The Dapr observability building block -description: A description of the observability building block, its features, benefits, and how to apply it +description: A description of the observability building block, its features and benefits, and how to apply it. author: edwinvw ms.date: 06/16/2021 --- @@ -36,7 +36,7 @@ As Dapr abstracts away the plumbing, the application is unaware of how observabi Dapr's [sidecar architecture](dapr-at-20000-feet.md#sidecar-architecture) enables built-in observability features. As services communicate, Dapr sidecars intercept the traffic and extract tracing, metrics, and logging information. Telemetry is published in an open standards format. By default, Dapr supports [OpenTelemetry](https://opentelemetry.io/) and [Zipkin](https://zipkin.io/). -Dapr provides [collectors](https://docs.dapr.io/operations/monitoring/tracing/open-telemetry-collector/) that can publish telemetry to different back-end monitoring tools. These tools present Dapr telemetry for analysis and querying. Figure 10-1 shows the Dapr observability architecture: +Dapr provides [collectors](https://docs.dapr.io/operations/monitoring/tracing/otel-collector/) that can publish telemetry to different back-end monitoring tools. These tools present Dapr telemetry for analysis and querying. Figure 10-1 shows the Dapr observability architecture: :::image type="content" source="./media/observability/observability-architecture.png" alt-text="Dapr observability architecture"::: diff --git a/docs/architecture/dapr-for-net-developers/publish-subscribe.md b/docs/architecture/dapr-for-net-developers/publish-subscribe.md index 8b2dcabf9036a..8c23f0db6e41b 100644 --- a/docs/architecture/dapr-for-net-developers/publish-subscribe.md +++ b/docs/architecture/dapr-for-net-developers/publish-subscribe.md @@ -216,13 +216,15 @@ The call to `MapSubscribeHandler` in the endpoint routing configuration will add Dapr [pub/sub components](https://github.com/dapr/components-contrib/tree/master/pubsub) handle the actual transport of the messages. Several are available. Each encapsulates a specific message broker product to implement the pub/sub functionality. At the time of writing, the following pub/sub components were available: - Apache Kafka +- AWS SNS/SQS - Azure Event Hubs - Azure Service Bus -- AWS SNS/SQS - GCP Pub/Sub - Hazelcast +- In Memory +- JetStream - MQTT -- NATS +- NATS Streaming - Pulsar - RabbitMQ - Redis Streams @@ -371,6 +373,10 @@ You can use Dapr pub/sub natively over HTTP or by using one of the language-spec With Dapr, you can plug a supported message broker product into your application. You can then swap message brokers without requiring code changes to your application. +### References + +- [Dapr supported pub/sub brokers](https://docs.dapr.io/reference/components-reference/supported-pubsub/) + > [!div class="step-by-step"] > [Previous](service-invocation.md) > [Next](bindings.md) diff --git a/docs/architecture/dapr-for-net-developers/reference-application.md b/docs/architecture/dapr-for-net-developers/reference-application.md index 831565af531dd..4fae000146c1f 100644 --- a/docs/architecture/dapr-for-net-developers/reference-application.md +++ b/docs/architecture/dapr-for-net-developers/reference-application.md @@ -487,7 +487,7 @@ public class DaprEventBus : IEventBus As you can see in the code snippet, the topic name is derived from event type's name. Because all eShop services use the `IEventBus` abstraction, retrofitting Dapr required *absolutely no change* to the mainline application code. > [!IMPORTANT] -> The Dapr SDK uses `System.Text.Json` to serialize/deserialize messages. However, `System.Text.Json` doesn't serialize properties of derived classes by default. In the eShop code, an event is sometimes explicitly declared as an `IntegrationEvent`, the base class for integration events. This construct allows the concrete event type to be determined dynamically at run time based on business logic. As a result, the event is serialized using the type information of the base class and not the derived class. To force `System.Text.Json` to serialize the properties of both the base and derived class, the code uses `object` as the generic type parameter. For more information, see the [.NET documentation](../../standard/serialization/system-text-json-polymorphism.md). +> The Dapr SDK uses `System.Text.Json` to serialize/deserialize messages. However, `System.Text.Json` doesn't serialize properties of derived classes by default. In the eShop code, an event is sometimes explicitly declared as an `IntegrationEvent`, the base class for integration events. This construct allows the concrete event type to be determined dynamically at run time based on business logic. As a result, the event is serialized using the type information of the base class and not the derived class. To force `System.Text.Json` to serialize the properties of both the base and derived class, the code uses `object` as the generic type parameter. For more information, see the [.NET documentation](../../standard/serialization/system-text-json/polymorphism.md). With Dapr, pub/sub infrastructure code is **dramatically simplified**. The application doesn't need to distinguish between message brokers. Dapr provides this abstraction for you. If needed, you can easily swap out message brokers or configure multiple message broker components with no code changes. diff --git a/docs/architecture/dapr-for-net-developers/secrets-management.md b/docs/architecture/dapr-for-net-developers/secrets-management.md index 0e91278702f6d..617459a724b41 100644 --- a/docs/architecture/dapr-for-net-developers/secrets-management.md +++ b/docs/architecture/dapr-for-net-developers/secrets-management.md @@ -147,16 +147,18 @@ public void GetCustomer(IConfiguration config) The secrets management building block supports several secret store components. At the time of writing, the following secret stores are available: -- Environment Variables -- Local file -- Kubernetes secrets +- AlibabaCloud OOS Parameter Store - AWS Secrets Manager +- AWS SSM Parameter Store - Azure Key Vault - GCP Secret Manager - HashiCorp Vault +- Kubernetes secrets +- Local environment variables +- Local file > [!IMPORTANT] -> The environment variables and local file components are designed for development workloads only. +> The local environment variables and file components are designed for development workloads only. The following sections show how to configure a secret store. @@ -734,6 +736,7 @@ You can use secret scopes to control access to specific secrets. ## References - [Beyond the Twelve-Factor Application](https://tanzu.vmware.com/content/blog/beyond-the-twelve-factor-app) +- [Dapr supported secret stores](https://docs.dapr.io/reference/components-reference/supported-secret-stores/) >[!div class="step-by-step"] >[Previous](observability.md) diff --git a/docs/architecture/dapr-for-net-developers/state-management.md b/docs/architecture/dapr-for-net-developers/state-management.md index 8aa0161efc98a..3a2c98c03e13b 100644 --- a/docs/architecture/dapr-for-net-developers/state-management.md +++ b/docs/architecture/dapr-for-net-developers/state-management.md @@ -240,23 +240,29 @@ At the time of this writing, Dapr provides support for the following transaction - Azure CosmosDB - Azure SQL Server +- CockroachDB +- In Memory - MongoDB +- MySQL +- Oracle Database - PostgreSQL - Redis +- RethinkDB Dapr also includes support for state stores that support CRUD operations, but not transactional capabilities: - Aerospike +- Apache Cassandra +- AWS DynamoDB - Azure Blob Storage - Azure Table Storage -- Cassandra -- Cloudstate - Couchbase -- etcd -- Google Cloud Firestore +- GCP Firestore - Hashicorp Consul - Hazelcast +- JetStream KV - Memcached +- Oracle Object Storage - Zookeeper ### Configuration @@ -427,7 +433,7 @@ In the Dapr Traffic Control sample application, the benefits of using Dapr state ### References -- [Dapr supported state stores](https://docs.dapr.io/operations/components/setup-state-store/supported-state-stores/) +- [Dapr supported state stores](https://docs.dapr.io/reference/components-reference/supported-state-stores/) > [!div class="step-by-step"] > [Previous](sample-application.md) diff --git a/docs/architecture/devops-for-aspnet-developers/actions-build.md b/docs/architecture/devops-for-aspnet-developers/actions-build.md index 3d0644b37eeb8..1f031df4bd2a5 100644 --- a/docs/architecture/devops-for-aspnet-developers/actions-build.md +++ b/docs/architecture/devops-for-aspnet-developers/actions-build.md @@ -100,11 +100,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Restore dependencies run: dotnet restore - name: Build @@ -119,8 +119,8 @@ Notice the following things: 1. The `on` object specifies when this workflow should run. This workflow has two events that trigger it: `push` to `main` and `pull_request` to `main`. Each time someone commits to `main` or creates a pull request (PR) to `main`, this workflow will execute. 1. There's a single `job` called `build`. This build should run on a hosted agent. `ubuntu_latest` specifies the most recent Ubuntu hosted agent. 1. There are five steps: - 1. `actions/checkout@v2` is an action that checks out the code in the repository onto the runner. - 1. `actions/setup-dotnet@v1` is an action that sets up the .NET CLI. This step also specifies a `name` attribute for the logs and the `dotnet-version` parameter within the `with` object. + 1. `actions/checkout@v3` is an action that checks out the code in the repository onto the runner. + 1. `actions/setup-dotnet@v3` is an action that sets up the .NET CLI. This step also specifies a `name` attribute for the logs and the `dotnet-version` parameter within the `with` object. 1. Three `run` steps that execute `dotnet restore`, `dotnet build`, and `dotnet test`. `name` attributes are also specified for these `run` steps to make the logs look pretty. ## Publish the output @@ -160,7 +160,7 @@ Now that you've successfully built and tested the code, add steps that publish t ```yml - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.2 + uses: actions/upload-artifact@v3 with: name: website path: SimpleFeedReader/website/** @@ -193,11 +193,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Restore dependencies run: dotnet restore - name: Build @@ -207,7 +207,7 @@ jobs: - name: Publish run: dotnet publish SimpleFeedReader/SimpleFeedReader.csproj -c Release -o website - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.2 + uses: actions/upload-artifact@v3 with: name: website path: SimpleFeedReader/website/** diff --git a/docs/architecture/devops-for-aspnet-developers/actions-codeql.md b/docs/architecture/devops-for-aspnet-developers/actions-codeql.md index a3d63e41dceb8..90c9e1c74897c 100644 --- a/docs/architecture/devops-for-aspnet-developers/actions-codeql.md +++ b/docs/architecture/devops-for-aspnet-developers/actions-codeql.md @@ -88,7 +88,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v1 diff --git a/docs/architecture/devops-for-aspnet-developers/actions-deploy.md b/docs/architecture/devops-for-aspnet-developers/actions-deploy.md index 841b7aeeb4d52..7d2aa608ca008 100644 --- a/docs/architecture/devops-for-aspnet-developers/actions-deploy.md +++ b/docs/architecture/devops-for-aspnet-developers/actions-deploy.md @@ -149,7 +149,7 @@ You can now add additional jobs to the workflow to deploy to the environments! Y steps: - name: Download a Build Artifact - uses: actions/download-artifact@v2.0.8 + uses: actions/download-artifact@v3 with: name: website path: website @@ -403,15 +403,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: 'Print manual run reason' if: ${{ github.event_name == 'workflow_dispatch' }} run: | echo 'Reason: ${{ github.event.inputs.reason }}' - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 2.1.x + dotnet-version: 6.0.x - name: Restore dependencies run: dotnet restore - name: Build @@ -421,7 +421,7 @@ jobs: - name: Publish run: dotnet publish SimpleFeedReader/SimpleFeedReader.csproj -c Release -o website - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.2 + uses: actions/upload-artifact@v3 with: name: website path: SimpleFeedReader/website/** @@ -437,7 +437,7 @@ jobs: steps: - name: Download a Build Artifact - uses: actions/download-artifact@v2.0.8 + uses: actions/download-artifact@v3 with: name: website path: website diff --git a/docs/architecture/devops-for-aspnet-developers/index.md b/docs/architecture/devops-for-aspnet-developers/index.md index 8599c77068353..52d1710d02fae 100644 --- a/docs/architecture/devops-for-aspnet-developers/index.md +++ b/docs/architecture/devops-for-aspnet-developers/index.md @@ -94,6 +94,8 @@ Use Azure's tools to monitor, troubleshoot, and tune your application. Other learning paths for the ASP.NET Core developer learning Azure. +[!INCLUDE [feedback](../includes/feedback.md)] + ## Additional introductory reading If this is your first exposure to cloud computing, these articles explain the basics. diff --git a/docs/architecture/grpc-for-wcf-developers/includes/download-alert.md b/docs/architecture/grpc-for-wcf-developers/includes/download-alert.md index f276f70524046..6a719dd56b0bf 100644 --- a/docs/architecture/grpc-for-wcf-developers/includes/download-alert.md +++ b/docs/architecture/grpc-for-wcf-developers/includes/download-alert.md @@ -8,12 +8,12 @@ ms.topic: include > [!TIP] > :::row::: > :::column span="3"::: -> This content is an excerpt from the eBook, Architecting Cloud Native .NET Applications for Azure, available on [.NET Docs](/dotnet/architecture/cloud-native) or as a free downloadable PDF that can be read offline. +> This content is an excerpt from the eBook, gRPC for WCF developers, available on [.NET Docs](/dotnet/architecture/grpc-for-wcf-developers) or as a free downloadable PDF that can be read offline. > > > [!div class="nextstepaction"] -> > [Download PDF](https://dotnet.microsoft.com/download/e-book/cloud-native-azure/pdf) +> > [Download PDF](https://dotnet.microsoft.com/download/e-book/grpc-for-wcf-devs/pdf) > :::column-end::: > :::column::: -> :::image type="content" source="../media/cover-thumbnail.png" alt-text="Cloud Native .NET apps for Azure eBook cover thumbnail."::: +> :::image type="content" source="../media/cover-thumbnail.png" alt-text="gRPC for WCF developers eBook cover thumbnail."::: > :::column-end::: > :::row-end::: diff --git a/docs/architecture/grpc-for-wcf-developers/index.md b/docs/architecture/grpc-for-wcf-developers/index.md index f20797f3f6bda..075495444ede6 100644 --- a/docs/architecture/grpc-for-wcf-developers/index.md +++ b/docs/architecture/grpc-for-wcf-developers/index.md @@ -60,12 +60,14 @@ This is a short introduction to building gRPC Services in ASP.NET Core 6.0, with Feel free to forward this guide to your team to help ensure a common understanding of these considerations and opportunities. Having everybody working from a common set of terms and underlying principles helps ensure consistent application of architectural patterns and practices. +[!INCLUDE [feedback](../includes/feedback.md)] + ## References - **gRPC website** - **Choosing between .NET 5 and .NET Framework for server apps** - [https://docs.microsoft.com/dotnet/standard/choosing-core-framework-server](../../standard/choosing-core-framework-server.md) + [https://learn.microsoft.com/dotnet/standard/choosing-core-framework-server](../../standard/choosing-core-framework-server.md) >[!div class="step-by-step"] >[Next](introduction.md) diff --git a/docs/architecture/includes/feedback.md b/docs/architecture/includes/feedback.md new file mode 100644 index 0000000000000..58c5429f034b3 --- /dev/null +++ b/docs/architecture/includes/feedback.md @@ -0,0 +1,12 @@ +## Send your feedback + +This book and related samples are constantly evolving, so your feedback is welcomed. If you have comments about how this book can be improved and you're reading this book on , use the **Feedback** section at the bottom of the page: + +:::image type="content" source="../media/feedback-footer.png" lightbox="../media/feedback-footer.png" alt-text="Feedback section of all .NET docs article footers."::: + +As highlighted in the preceding screen capture, the feedback section allows you to submit feedback for: + +- **This product**: using the .NET product feedback form. +- **This page**: using a GitHub issue template with the page details. + +If you're reading this book as a PDF, you can submit feedback by creating a new [.NET Docs: GitHub issue](https://github.com/dotnet/docs/issues) or by using the [.NET Architecture eBooks: GitHub issue template](https://aka.ms/ebookfeedback). diff --git a/docs/architecture/index.yml b/docs/architecture/index.yml index 1d230ebc1d4cd..0db8bf4210506 100644 --- a/docs/architecture/index.yml +++ b/docs/architecture/index.yml @@ -26,6 +26,16 @@ landingContent: - text: Porting existing ASP.NET apps to .NET 6 url: porting-existing-aspnet-apps/index.md + # Card + - title: Migrate .NET apps to Azure + linkLists: + - linkListType: concept + links: + - text: Modernize existing .NET apps with Azure cloud and Windows containers + url: modernize-with-azure-containers/index.md + - text: Migrate your .NET app to Azure + url: https://dotnet.microsoft.com/apps/cloud/migrate-to-azure + # Card - title: Develop cloud-native .NET apps for Azure linkLists: @@ -44,21 +54,11 @@ landingContent: - text: Hello World Microservice tutorial url: https://dotnet.microsoft.com/learn/aspnet/microservice-tutorial/intro - text: Create and deploy a cloud-native ASP.NET Core microservice - url: /learn/modules/microservices-aspnet-core + url: /training/modules/microservices-aspnet-core - text: Deploy a cloud-native ASP.NET Core microservice with GitHub Actions - url: /learn/modules/microservices-devops-aspnet-core + url: /training/modules/microservices-devops-aspnet-core - text: Implement resiliency in a cloud-native ASP.NET Core microservice - url: /learn/modules/microservices-resiliency-aspnet-core - - # Card - - title: Migrate .NET apps to Azure - linkLists: - - linkListType: concept - links: - - text: Modernize existing .NET apps with Azure cloud and Windows containers - url: modernize-with-azure-containers/index.md - - text: Migrate your .NET app to Azure - url: https://dotnet.microsoft.com/apps/cloud/migrate-to-azure + url: /training/modules/microservices-resiliency-aspnet-core # Card - title: Understand DevOps for .NET @@ -66,14 +66,20 @@ landingContent: - linkListType: concept links: - text: Containerized Docker application lifecycle with Microsoft platform and tools - url: containerized-lifecycle/index.md + url: containerized-lifecycle/index.md - text: DevOps for ASP.NET Core Developers - url: devops-for-aspnet-developers/index.md + url: devops-for-aspnet-developers/index.md # Card - - title: Modernize desktop apps + - title: Mobile and desktop apps linkLists: - linkListType: concept links: - text: Modernizing desktop apps on Windows with .NET 6 url: modernize-desktop/index.md + - text: Enterprise Application Patterns Using .NET MAUI + url: maui/index.md + - linkListType: learn + links: + - text: Build mobile and desktop apps with .NET MAUI + url: /training/paths/build-apps-with-dotnet-maui diff --git a/docs/architecture/maui/accessing-remote-data.md b/docs/architecture/maui/accessing-remote-data.md new file mode 100644 index 0000000000000..ece36c812275d --- /dev/null +++ b/docs/architecture/maui/accessing-remote-data.md @@ -0,0 +1,385 @@ +--- +title: Accessing Remote Data +description: Communicating with Web APIs in .NET MAUI +author: michaelstonis +no-loc: [MAUI] +ms.date: 07/12/2022 +--- + +# Accessing remote data + +[!INCLUDE [download-alert](includes/download-alert.md)] + +Many modern web-based solutions make use of web services, hosted by web servers, to provide functionality for remote client applications. The operations that a web service exposes constitute a web API. + +Client apps should be able to utilize the web API without knowing how the data or operations that the API exposes are implemented. This requires that the API abides by common standards that enable a client app and web service to agree on which data formats to use, and the structure of the data that is exchanged between client apps and the web service. + +## Introduction to Representational State Transfer + +Representational State Transfer (REST) is an architectural style for building distributed systems based on hypermedia. A primary advantage of the REST model is that it's based on open standards and doesn't bind the implementation of the model or the client apps that access it to any specific implementation. Therefore, a REST web service could be implemented using [Microsoft ASP.NET Core](/aspnet/core/introduction-to-aspnet-core), and client apps could be developing using any language and toolset that can generate HTTP requests and parse HTTP responses. + +The REST model uses a navigational scheme to represent objects and services over a network, referred to as resources. Systems that implement REST typically use the HTTP protocol to transmit requests to access these resources. In such systems, a client app submits a request in the form of a URI that identifies a resource, and an HTTP method (such as GET, POST, PUT, or DELETE) that indicates the operation to be performed on that resource. The body of the HTTP request contains any data required to perform the operation. + +> [!NOTE] +> REST defines a stateless request model. Therefore, HTTP requests must be independent and might occur in any order. + +The response from a REST request makes use of standard HTTP status codes. For example, a request that returns valid data should include the HTTP response code 200 (`OK`), while a request that fails to find or delete a specified resource should return a response that includes the HTTP status code 404 (`Not Found`). + +A RESTful web API exposes a set of connected resources, and provides the core operations that enable an app to manipulate those resources and easily navigate between them. For this reason, the URIs that constitute a typical RESTful web API are oriented towards the data that it exposes, and use the facilities provided by HTTP to operate on this data. + +The data included by a client app in an HTTP request, and the corresponding response messages from the web server, could be presented in a variety of formats, known as media types. When a client app sends a request that returns data in the body of a message, it can specify the media types it can handle in the Accept header of the request. If the web server supports this media type, it can reply with a response that includes the Content-Type header that specifies the format of the data in the body of the message. It's then the responsibility of the client app to parse the response message and interpret the results in the message body appropriately. + +For more information about REST, see [API design](/azure/architecture/best-practices/api-design) and [API implementation](/azure/architecture/best-practices/api-implementation) on Microsoft Docs. + +## Consuming RESTful APIs + +The eShopOnContainers multi-platform app uses the Model-View-ViewModel (MVVM) pattern, and the model elements of the pattern represent the domain entities used in the app. The controller and repository classes in the eShopOnContainers reference application accept and return many of these model objects. Therefore, they are used as data transfer objects (DTOs) that hold all the data that is passed between the app and the containerized microservices. The main benefit of using DTOs to pass data to and receive data from a web service is that by transmitting more data in a single remote call, the app can reduce the number of remote calls that need to be made. + +## Making web requests + +The eShopOnContainers multi-platform app uses the `HttpClient` class to make requests over HTTP, with JSON being used as the media type. This class provides functionality for asynchronously sending HTTP requests and receiving HTTP responses from a URI identified resource. The HttpResponseMessage class represents an HTTP response message received from a REST API after an HTTP request has been made. It contains information about the response, including the status code, headers, and any body. The HttpContent class represents the HTTP body and content headers, such as Content-Type and Content-Encoding. The content can be read using any of the `ReadAs` methods, such as `ReadAsStringAsync` and `ReadAsByteArrayAsync`, depending on the format of the data. + +## Making a GET request + +The `CatalogService` class is used to manage the data retrieval process from the catalog microservice. In the `RegisterViewModels` method in the `MauiProgram` class, the `CatalogService` class is registered as a type mapping against the `ICatalogService` type with the dependency injection container. Then, when an instance of the `CatalogViewModel` class is created, its constructor accepts an `ICatalogService type`, which the dependency injection container resolves, returning an instance of the `CatalogService` class. For more information about dependency injection, see [Dependency Injection](dependency-injection.md). + +The image below shows the interaction of classes that read catalog data from the catalog microservice for displaying by the CatalogView. + +![Retrieving data from the catalog microservice.](./media/retrieving-data-for-catalog.png) + +When the `CatalogView` is navigated to, the `OnInitialize` method in the CatalogViewModel class is called. This method retrieves catalog data from the catalog microservice, as demonstrated in the following code example: + +```csharp +public override async Task InitializeAsync() +{ + Products = await _productsService.GetCatalogAsync(); +} +``` + +This method calls the `GetCatalogAsync` method of the `CatalogService` instance that was injected into the `CatalogViewModel` by the dependency injection container. The following code example shows the `GetCatalogAsync` method: + +```csharp +public async Task> GetCatalogAsync() +{ + UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint); + builder.Path = "api/v1/catalog/items"; + string uri = builder.ToString(); + + CatalogRoot? catalog = await _requestProvider.GetAsync(uri); + + return catalog?.Data; +} +``` + +This method builds the URI that identifies the resource the request will be sent to, and uses the `RequestProvider` class to invoke the GET HTTP method on the resource, before returning the results to the `CatalogViewModel`. The `RequestProvider` class contains functionality that submits a request in the form of a URI that identifies a resource, an HTTP method that indicates the operation to be performed on that resource, and a body that contains any data required to perform the operation. For information about how the `RequestProvider` class is injected into the `CatalogService` class, see [Dependency Injection](dependency-injection.md). + +The following code example shows the `GetAsync` method in the `RequestProvider` class: + +```csharp +public async Task GetAsync(string uri, string token = "") +{ + HttpClient httpClient = GetOrCreateHttpClient(token); + HttpResponseMessage response = await httpClient.GetAsync(uri); + + await HandleResponse(response); + TResult result = await response.Content.ReadFromJsonAsync(); + + return result; +} +``` + +This method calls the `GetOrCreateHttpClient` method, which returns an instance of the `HttpClient` class with the appropriate headers set. It then submits an asynchronous `GET` request to the resource identified by the URI, with the response being stored in the `HttpResponseMessage` instance. The `HandleResponse` method is then invoked, which throws an exception if the response doesn't include a success HTTP status code. Then the response is read as a string, converted from JSON to a `CatalogRoot` object, and returned to the `CatalogService`. + +The `GetOrCreateHttpClient` method is shown in the following code example: + +```csharp +private readonly Lazy _httpClient = + new Lazy( + () => + { + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + return httpClient; + }, + LazyThreadSafetyMode.ExecutionAndPublication); + +private HttpClient GetOrCreateHttpClient(string token = "") + { + var httpClient = _httpClient.Value; + + if (!string.IsNullOrEmpty(token)) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + else + { + httpClient.DefaultRequestHeaders.Authorization = null; + } + + return httpClient; + } +``` + +This method uses creates a new instance or retrieves a cached instance of the `HttpClient` class, and sets the Accept header of any requests made by the `HttpClient` instance to `application/json`, which indicates that it expects the content of any response to be formatted using JSON. Then, if an access token was passed as an argument to the `GetOrCreateHttpClient` method, it's added to the `Authorization` header of any requests made by the `HttpClient` instance, prefixed with the string `Bearer`. For more information about authorization, see [Authorization](authentication-and-authorization.md). + +> [!TIP] +> It is highly recommended to cache and reuse instances of the `HttpClient` for better application performance. Creating a new `HttpClient` for each operation can lead to issue with socket exhaustion. For more information, see [HttpClient Instancing](/dotnet/api/system.net.http.httpclient#instancing) on the Microsoft Developer Center. + +When the `GetAsync` method in the `RequestProvider` class calls `HttpClient.GetAsync`, the `Items` method in the `CatalogController` class in the `Catalog.API` project is invoked, which is shown in the following code example: + +```csharp +[HttpGet] +[Route("[action]")] +public async Task Items( + [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) +{ + var totalItems = await _catalogContext.CatalogItems + .LongCountAsync(); + + var itemsOnPage = await _catalogContext.CatalogItems + .OrderBy(c => c.Name) + .Skip(pageSize * pageIndex) + .Take(pageSize) + .ToListAsync(); + + itemsOnPage = ComposePicUri(itemsOnPage); + var model = new PaginatedItemsViewModel( + pageIndex, pageSize, totalItems, itemsOnPage); + + return Ok(model); +} +``` + +This method retrieves the catalog data from the SQL database using [EntityFramework](/ef/), and returns it as a response message that includes a success HTTP status code, and a collection of JSON formatted `CatalogItem` instances. + +## Making a POST request + +The `BasketService` class is used to manage the data retrieval and update process with the basket microservice. In the `RegisterAppServices` method in the `MauiProgram` class, the `BasketService` class is registered as a type mapping against the `IBasketService` type with the dependency injection container. Then, when an instance of the `BasketViewModel` class is created, its constructor accepts an `IBasketService` type, which the dependency injection container resolves, returning an instance of the `BasketService` class. For more information about dependency injection, see [Dependency Injection](dependency-injection.md). + +The image below shows the interaction of classes that send the basket data displayed by the BasketView, to the basket microservice. + +![Sending data to the basket microservice.](./media/sending-data-to-the-basket-microservice.png) + +When an item is added to the shopping basket, the `ReCalculateTotalAsync` method in the `BasketViewModel` class is called. This method updates the total value of items in the basket, and sends the basket data to the basket microservice, as demonstrated in the following code example: + +```csharp +private async Task ReCalculateTotalAsync() +{ + // Omitted for brevity... + + await _basketService.UpdateBasketAsync( + new CustomerBasket + { + BuyerId = userInfo.UserId, + Items = BasketItems.ToList() + }, + authToken); +} +``` + +This method calls the `UpdateBasketAsync` method of the `BasketService` instance that was injected into the `BasketViewModel` by the dependency injection container. The following method shows the `UpdateBasketAsync` method: + +```csharp +public async Task UpdateBasketAsync( + CustomerBasket customerBasket, string token) +{ + UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint); + string uri = builder.ToString(); + var result = await _requestProvider.PostAsync(uri, customerBasket, token); + return result; +} +``` + +This method builds the URI that identifies the resource the request will be sent to, and uses the `RequestProvider` class to invoke the POST HTTP method on the resource, before returning the results to the `BasketViewModel`. Note that an access token, obtained from `IdentityServer` during the authentication process, is required to authorize requests to the basket microservice. For more information about authorization, see [Authorization](authentication-and-authorization.md). + +The following code example shows one of the `PostAsync` methods in the `RequestProvider` class: + +```csharp +public async Task PostAsync( + string uri, TResult data, string token = "", string header = "") +{ + HttpClient httpClient = GetOrCreateHttpClient(token); + + var content = new StringContent(JsonSerializer.Serialize(data)); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + HttpResponseMessage response = await httpClient.PostAsync(uri, content); + + await HandleResponse(response); + TResult result = await response.Content.ReadFromJsonAsync(); + + return result; +} +``` + +This method calls the `GetOrCreateHttpClient` method, which returns an instance of the `HttpClient` class with the appropriate headers set. It then submits an asynchronous POST request to the resource identified by the URI, with the serialized basket data being sent in JSON format, and the response being stored in the `HttpResponseMessage` instance. The `HandleResponse` method is then invoked, which throws an exception if the response doesn't include a success HTTP status code. Then, the response is read as a string, converted from JSON to a `CustomerBasket` object, and returned to the BasketService. For more information about the `GetOrCreateHttpClient` method, see [Making a GET request](#making-a-get-request). + +When the `PostAsync` method in the `RequestProvider` class calls `HttpClient.PostAsync`, the `Post` method in the `BasketController` class in the `Basket.API` project is invoked, which is shown in the following code example: + +```csharp +[HttpPost] +public async Task Post([FromBody] CustomerBasket value) +{ + var basket = await _repository.UpdateBasketAsync(value); + return Ok(basket); +} +``` + +This method uses an instance of the `RedisBasketRepository` class to persist the basket data to the Redis cache, and returns it as a response message that includes a success HTTP status code, and a JSON formatted `CustomerBasket` instance. + +## Making a DELETE request + +The image below shows the interactions of classes that delete basket data from the basket microservice, for the `CheckoutView`. + +![Deleting data from the basket microservice.](./media/deleting-data-from-the-basket-microservice.png) + +When the checkout process is invoked, the `CheckoutAsync` method in the `CheckoutViewModel` class is called. This method creates a new order, before clearing the shopping basket, as demonstrated in the following code example: + +```csharp +private async Task CheckoutAsync() +{ + // Omitted for brevity... + + await _basketService.ClearBasketAsync( + _shippingAddress.Id.ToString(), authToken); +} +``` + +This method calls the `ClearBasketAsync` method of the `BasketService` instance that was injected into the `CheckoutViewModel` by the dependency injection container. The following method shows the `ClearBasketAsync` method: + +```csharp +public async Task ClearBasketAsync(string guidUser, string token) +{ + UriBuilder builder = new(GlobalSetting.Instance.BasketEndpoint); + builder.Path = guidUser; + string uri = builder.ToString(); + await _requestProvider.DeleteAsync(uri, token); +} +``` + +This method builds the URI that identifies the resource that the request will be sent to, and uses the `RequestProvider` class to invoke the `DELETE` HTTP method on the resource. Note that an access token, obtained from `IdentityServer` during the authentication process, is required to authorize requests to the basket microservice. For more information about authorization, see [Authorization](authentication-and-authorization.md). + +The following code example shows the `DeleteAsync` method in the `RequestProvider` class: + +```csharp +public async Task DeleteAsync(string uri, string token = "") +{ + HttpClient httpClient = GetOrCreateHttpClient(token); + await httpClient.DeleteAsync(uri); +} +``` + +This method calls the `GetOrCreateHttpClient` method, which returns an instance of the `HttpClient` class with the appropriate headers set. It then submits an asynchronous `DELETE` request to the resource identified by the URI. For more information about the `GetOrCreateHttpClient` method, see [Making a GET request](#making-a-get-request). + +When the `DeleteAsync` method in the `RequestProvider` class calls `HttpClient.DeleteAsync`, the `Delete` method in the `BasketController` class in the `Basket.API` project is invoked, which is shown in the following code example: + +```csharp +[HttpDelete("{id}")] +public void Delete(string id) => + _repository.DeleteBasketAsync(id); +``` + +This method uses an instance of the `RedisBasketRepository` class to delete the basket data from the Redis cache. + +## Caching data + +The performance of an app can be improved by caching frequently accessed data to fast storage that's located close to the app. If the fast storage is located closer to the app than the original source, then caching can significantly improve response times when retrieving data. + +The most common form of caching is read-through caching, where an app retrieves data by referencing the cache. If the data isn't in the cache, it's retrieved from the data store and added to the cache. Apps can implement read-through caching with the cache-aside pattern. This pattern determines whether the item is currently in the cache. If the item isn't in the cache, it's read from the data store and added to the cache. For more information, see the [Cache-Aside](/azure/architecture/patterns/cache-aside) pattern on Microsoft Docs. + +> [!TIP] +> Cache data that's read frequently and changes infrequently. + +This data can be added to the cache on demand the first time it is retrieved by an app. This means that the app needs to fetch the data only once from the data store, and that subsequent access can be satisfied by using the cache. + +Distributed applications, such as the eShopOnContainers reference application, should provide either or both of the following caches: + +- A shared cache, which can be accessed by multiple processes or machines. +- A private cache, where data is held locally on the device running the app. + +The eShopOnContainers multi-platform app uses a private cache, where data is held locally on the device that's running an instance of the app. For information about the cache used by the eShopOnContainers reference application, see [.NET Microservices: Architecture for Containerized .NET Applications](https://aka.ms/microservicesebook). + +> [!TIP] +> Think of the cache as a transient data store that could disappear at any time. + +Ensure that data is maintained in the original data store as well as the cache. The chances of losing data are then minimized if the cache becomes unavailable. + +## Managing data expiration + +It's impractical to expect that cached data will always be consistent with the original data. Data in the original data store might change after it's been cached, causing the cached data to become stale. Therefore, apps should implement a strategy that helps to ensure that the data in the cache is as up-to-date as possible, but can also detect and handle situations that arise when the data in the cache has become stale. Most caching mechanisms enable the cache to be configured to expire data, and hence reduce the period for which data might be out of date. + +> [!TIP] +> Set a default expiration time when configuring a cache. + +Many caches implement expiration, which invalidates data and removes it from the cache if it's not accessed for a specified period. However, care must be taken when choosing the expiration period. If it's made too short, data will expire too quickly and the benefits of caching will be reduced. If it's made too long, the data risks becoming stale. Therefore, the expiration time should match the pattern of access for apps that use the data. + +When cached data expires, it should be removed from the cache, and the app must retrieve the data from the original data store and place it back into the cache. + +It's also possible that a cache might fill up if data is allowed to remain for too long a period. Therefore, requests to add new items to the cache might be required to remove some items in a process known as *eviction*. Caching services typically evict data on a least-recently-used basis. However, there are other eviction policies, including most-recently-used, and first-in-first-out. For more information, see [Caching Guidance](/azure/architecture/best-practices/caching) on Microsoft Docs. + +## Caching images + +The eShopOnContainers multi-platform app consumes remote product images that benefit from being cached. These images are displayed by the Image control. The .NET MAUI Image control supports caching of downloaded images which has caching enabled by default, and will store the image locally for 24 hours. In addition, the expiration time can be configured with the CacheValidity property. For more information, see [Downloaded Image Caching](/dotnet/maui/user-interface/controls/image#image-caching) on the Microsoft Developer Center. + +## Increasing resilience + +All apps that communicate with remote services and resources must be sensitive to transient faults. Transient faults include the momentary loss of network connectivity to services, the temporary unavailability of a service, or timeouts that arise when a service is busy. These faults are often self-correcting, and if the action is repeated after a suitable delay it's likely to succeed. + +Transient faults can have a huge impact on the perceived quality of an app, even if it has been thoroughly tested under all foreseeable circumstances. To ensure that an app that communicates with remote services operates reliably, it must be able to do all of the following: + +- Detect faults when they occur, and determine if the faults are likely to be transient. +- Retry the operation if it determines that the fault is likely to be transient and keep track of the number of times the operation was retried. +- Use an appropriate retry strategy, which specifies the number of retries, the delay between each attempt, and the actions to take after a failed attempt. + +This transient fault handling can be achieved by wrapping all attempts to access a remote service in code that implements the retry pattern. + +## Retry pattern + +If an app detects a failure when it tries to send a request to a remote service, it can handle the failure in any of the following ways: + +- Retrying the operation. The app could retry the failing request immediately. +- Retrying the operation after a delay. The app should wait for a suitable amount of time before retrying the request. +- Cancelling the operation. The application should cancel the operation and report an exception. + +The retry strategy should be tuned to match the business requirements of the app. For example, it's important to optimize the retry count and retry interval to the operation being attempted. If the operation is part of a user interaction, the retry interval should be short and only a few retries attempted to avoid making users wait for a response. If the operation is part of a long running workflow, where cancelling or restarting the workflow is expensive or time-consuming, it's appropriate to wait longer between attempts and to retry more times. + +> [!NOTE] +> An aggressive retry strategy with minimal delay between attempts, and a large number of retries, could degrade a remote service that's running close to or at capacity. In addition, such a retry strategy could also affect the responsiveness of the app if it's continually trying to perform a failing operation. + +If a request still fails after a number of retries, it's better for the app to prevent further requests going to the same resource and to report a failure. Then, after a set period, the app can make one or more requests to the resource to see if they're successful. For more information, see [Circuit breaker pattern](#circuit-breaker-pattern). + +> [!TIP] +> Never implement an endless retry mechanism. Instead, prefer an exponential backoff. + +Use a finite number of retries, or implement the [Circuit Breaker](/azure/architecture/patterns/circuit-breaker) pattern to allow a service to recover. + +The eShopOnContainers reference application does implement the retry pattern. For more information, including a discussion of how to combine the retry pattern with the HttpClient class, see [.NET Microservices: Architecture for Containerized .NET Applications](https://aka.ms/microservicesebook). + +For more information about the retry pattern, see the [Retry](/azure/architecture/patterns/retry) pattern on Microsoft Docs. + +## Circuit breaker pattern + +In some situations, faults can occur due to anticipated events that take longer to fix. These faults can range from a partial loss of connectivity to the complete failure of a service. In these situations, it's pointless for an app to retry an operation that's unlikely to succeed, and instead should accept that the operation has failed and handle this failure accordingly. + +The circuit breaker pattern can prevent an app from repeatedly trying to execute an operation that's likely to fail, while also enabling the app to detect whether the fault has been resolved. + +> [!NOTE] +> The purpose of the circuit breaker pattern is different from the retry pattern. The retry pattern enables an app to retry an operation in the expectation that it'll succeed. The circuit breaker pattern prevents an app from performing an operation that's likely to fail. + +A circuit breaker acts as a proxy for operations that might fail. The proxy should monitor the number of recent failures that have occurred, and use this information to decide whether to allow the operation to proceed, or to return an exception immediately. + +The eShopOnContainers multi-platform app does not currently implement the circuit breaker pattern. However, the eShopOnContainers does. For more information, see [.NET Microservices: Architecture for Containerized .NET Applications](https://aka.ms/microservicesebook). + +> [!TIP] +> Combine the retry and circuit breaker patterns. + +An app can combine the retry and circuit breaker patterns by using the retry pattern to invoke an operation through a circuit breaker. However, the retry logic should be sensitive to any exceptions returned by the circuit breaker and abandon retry attempts if the circuit breaker indicates that a fault is not transient. + +For more information about the circuit breaker pattern, see the [Circuit Breaker](/azure/architecture/patterns/circuit-breaker) pattern on Microsoft Docs. + +## Summary + +Many modern web-based solutions make use of web services, hosted by web servers, to provide functionality for remote client applications. The operations that a web service exposes constitute a web API, and client apps should be able to utilize the web API without knowing how the data or operations that the API exposes are implemented. + +The performance of an app can be improved by caching frequently accessed data to fast storage that's located close to the app. Apps can implement read-through caching with the cache-aside pattern. This pattern determines whether the item is currently in the cache. If the item isn't in the cache, it's read from the data store and added to the cache. + +When communicating with web APIs, apps must be sensitive to transient faults. Transient faults include the momentary loss of network connectivity to services, the temporary unavailability of a service, or timeouts that arise when a service is busy. These faults are often self-correcting, and if the action is repeated after a suitable delay, then it's likely to succeed. Therefore, apps should wrap all attempts to access a web API in code that implements a transient fault handling mechanism. diff --git a/docs/architecture/maui/authentication-and-authorization.md b/docs/architecture/maui/authentication-and-authorization.md new file mode 100644 index 0000000000000..ce43b105abe64 --- /dev/null +++ b/docs/architecture/maui/authentication-and-authorization.md @@ -0,0 +1,476 @@ +--- +title: Authentication and Authorization +description: Providing security and identity management for .NET MAUI application +author: michaelstonis +no-loc: [MAUI] +ms.date: 07/12/2022 +--- + +# Authentication and Authorization + +[!INCLUDE [download-alert](includes/download-alert.md)] + +Authentication is the process of obtaining identification credentials such as name and password from a user and validating those credentials against an authority. The entity that submitted the credentials is considered an authenticated identity if the credentials are valid. Once an identity has been established, an authorization process determines whether that identity has access to a given resource. + +There are many approaches to integrating authentication and authorization into a .NET MAUI app that communicates with an ASP.NET web application, including using ASP.NET Core Identity, external authentication providers such as Microsoft, Google, Facebook, or Twitter, and authentication middleware. The eShopOnContainers multi-platform app performs authentication and authorization with a containerized identity microservice that uses IdentityServer 4. The app requests security tokens from IdentityServer to authenticate a user or access a resource. For IdentityServer to issue tokens on behalf of a user, the user must sign in to IdentityServer. However, IdentityServer doesn't provide a user interface or database for authentication. Therefore, in the eShopOnContainers reference application, ASP.NET Core Identity is used for this purpose. + +## Authentication + +Authentication is required when an application needs to know the current user's identity. ASP.NET Core's primary mechanism for identifying users is the ASP.NET Core Identity membership system, which stores user information in a data store configured by the developer. Typically, this data store will be an EntityFramework store, though custom stores or third-party packages can be used to store identity information in Azure storage, DocumentDB, or other locations. + +For authentication scenarios that use a local user datastore and persist identity information between requests via cookies (as is typical in ASP.NET web applications), ASP.NET Core Identity is a suitable solution. However, cookies are not always a natural means of persisting and transmitting data. For example, an ASP.NET Core web application that exposes RESTful endpoints that are accessed from an app will typically need to use bearer token authentication since cookies can't be used in this scenario. However, bearer tokens can easily be retrieved and included in the authorization header of web requests made from the app. + +### Issuing bearer tokens using IdentityServer 4 + +[IdentityServer 4](https://github.com/IdentityServer/IdentityServer4) is an open source OpenID Connect and OAuth 2.0 framework for ASP.NET Core, which can be used for many authentication and authorization scenarios, including issuing security tokens for local ASP.NET Core Identity users. + +> [!NOTE] +> OpenID Connect and OAuth 2.0 are very similar, while having different responsibilities. + +OpenID Connect is an authentication layer on top of the OAuth 2.0 protocol. OAuth 2 is a protocol that allows applications to request access tokens from a security token service and use them to communicate with APIs. This delegation reduces complexity in both client applications and APIs since authentication and authorization can be centralized. + +OpenID Connect and OAuth 2.0 combine the two fundamental security concerns of authentication and API access, and IdentityServer 4 is an implementation of these protocols. + +In applications that use direct client-to-microservice communication, such as the eShopOnContainers reference application, a dedicated authentication microservice acting as a Security Token Service (STS) can be used to authenticate users, as shown in the following diagram. For more information about direct client-to-microservice communication, see [Microservices](micro-services.md). + +![Authentication by a dedicated authentication microservice.](./media/authentication-dedicated-authentication-microservice.png) + +The eShopOnContainers multi-platform app communicates with the identity microservice, which uses IdentityServer 4 to perform authentication, and access control for APIs. Therefore, the multi-platform app requests tokens from IdentityServer, either for authenticating a user or for accessing a resource: + +- Authenticating users with IdentityServer is achieved by the multi-platform app requesting an _identity_ token, representing an authentication process's outcome. At a minimum, it contains an identifier for the user and information about how and when the user is authenticated. It can also include additional identity data. +- Accessing a resource with IdentityServer is achieved by the multi-platform app requesting an _access_ token, which allows access to an API resource. Clients request access tokens and forward them to the API. Access tokens contain information about the client and the user, if present. APIs then use that information to authorize access to their data. + +> [!NOTE] +> A client must be registered with IdentityServer before it can successfully request tokens. For more information on adding clients, see [Defining Clients](https://identityserver4.readthedocs.io/en/latest/topics/clients.html). + +### Adding IdentityServer to a web application + +In order for an ASP.NET Core web application to use IdentityServer 4, it must be added to the web application's Visual Studio solution. For more information, see [Setup and Overview](https://identityserver4.readthedocs.io/en/latest/quickstarts/0_overview.html) in the IdentityServer documentation. +Once IdentityServer is included in the web application's Visual Studio solution, it must be added to its HTTP request processing pipeline to serve requests to OpenID Connect and OAuth 2.0 endpoints. This is achieved in the `Configure` method in the web application's `Startup` class, as demonstrated in the following code example: + +```csharp +public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) +{ +    app.UseIdentity(); +} +``` + +Order matters in the web application's HTTP request processing pipeline. Therefore, IdentityServer must be added to the pipeline before the UI framework that implements the login screen. + +### Configuring IdentityServer + +IdentityServer should be configured in the ConfigureServices method in the web application's Startup class by calling the `services.AddIdentityServer` method, as demonstrated in the following code example from the eShopOnContainers reference application: + +```csharp +public void ConfigureServices(IServiceCollection services) +{ +    services + .AddIdentityServer(x => x.IssuerUri = "null") +        .AddSigningCredential(Certificate.Get()) +        .AddAspNetIdentity() +        .AddConfigurationStore(builder => +            builder.UseSqlServer(connectionString, options => +                options.MigrationsAssembly(migrationsAssembly))) +        .AddOperationalStore(builder => +            builder.UseSqlServer(connectionString, options => +                options.MigrationsAssembly(migrationsAssembly))) +        .Services.AddTransient(); +} +``` + +After calling the `services.AddIdentityServer` method, additional fluent APIs are called to configure the following: + +- Credentials used for signing. +- API and identity resources that users might request access to. +- Clients that will be connecting to request tokens. +- ASP.NET Core Identity. + +> [!TIP] +> Dynamically load the IdentityServer 4 configuration. IdentityServer 4's APIs allow for configuring IdentityServer from an in-memory list of configuration objects. In the eShopOnContainers reference application, these in-memory collections are hard-coded into the application. However, in production scenarios they can be loaded dynamically from a configuration file or from a database. + +For information about configuring IdentityServer to use ASP.NET Core Identity, see [Using ASP.NET Core Identity](https://identityserver4.readthedocs.io/en/latest/quickstarts/6_aspnet_identity.html) in the IdentityServer documentation. + +### Configuring API resources + +When configuring API resources, the `AddInMemoryApiResources` method expects an `IEnumerable` collection. The following code example shows the `GetApis` method that provides this collection in the eShopOnContainers reference application: + +```csharp +public static IEnumerable GetApis() +{ +    return new List +    { +        new ApiResource("orders", "Orders Service"), +        new ApiResource("basket", "Basket Service") +    }; +} +``` + +This method specifies that IdentityServer should protect the orders and basket APIs. Therefore, IdentityServer-managed access tokens will be required when making calls to these APIs. For more information about the `ApiResource` type, see [API Resource](https://identityserver4.readthedocs.io/en/latest/reference/api_resource.html#refapiresource) in the IdentityServer 4 documentation. + +### Configuring identity resources + +When configuring identity resources, the `AddInMemoryIdentityResources` method expects an `IEnumerable` collection. Identity resources are data such as user ID, name, or email address. Each identity resource has a unique name, and arbitrary claim types can be assigned to it, which will be included in the identity token for the user. The following code example shows the `GetResources` method that provides this collection in the eShopOnContainers reference application: + +```csharp +public static IEnumerable GetResources() +{ +    return new List +    { +        new IdentityResources.OpenId(), +        new IdentityResources.Profile() +    }; +} +``` + +The OpenID Connect specification specifies some [standard identity resources](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims). The minimum requirement is that support is provided for emitting a unique ID for users. This is achieved by exposing the `IdentityResources.OpenId` identity resource. + +> [!NOTE] +> The IdentityResources class supports all of the scopes defined in the OpenID Connect specification (openid, email, profile, telephone, and address). + +IdentityServer also supports defining custom identity resources. For more information, see [Defining custom identity resources](https://identityserver4.readthedocs.io/en/latest/topics/resources.html#defining-custom-identity-resources) in the IdentityServer documentation. For more information about the IdentityResource type, see [Identity Resource](https://identityserver4.readthedocs.io/en/latest/reference/identity_resource.html) in the IdentityServer 4 documentation. + +### Configuring clients + +Clients are applications that can request tokens from IdentityServer. Typically, the following settings must be defined for each client as a minimum: + +- A unique client ID. +- The allowed interactions with the token service (known as the grant type). +- The location where identity and access tokens are sent to (known as a redirect URI). +- A list of resources that the client is allowed access to (known as scopes). + +When configuring clients, the `AddInMemoryClients` method expects an `IEnumerable` collection. The following code example shows the configuration for the eShopOnContainers multi-platform app in the `GetClients` method that provides this collection in the eShopOnContainers reference application: + +```csharp +public static IEnumerable GetClients(Dictionary clientsUrl) +{ +    return new List +    { +        // Omitted for brevity +        new Client +        { +            ClientId = "xamarin", +            ClientName = "eShop Xamarin OpenId Client", +            AllowedGrantTypes = GrantTypes.Hybrid, +            ClientSecrets = +            { +                new Secret("secret".Sha256()) +            }, +            RedirectUris = { clientsUrl["Xamarin"] }, +            RequireConsent = false, +            RequirePkce = true, +            PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" }, +            AllowedCorsOrigins = { "http://eshopxamarin" }, +            AllowedScopes = new List +            { +                IdentityServerConstants.StandardScopes.OpenId, +                IdentityServerConstants.StandardScopes.Profile, +                IdentityServerConstants.StandardScopes.OfflineAccess, +                "orders", +                "basket" +            }, +            AllowOfflineAccess = true, +            AllowAccessTokensViaBrowser = true +        }, +    }; +} +``` + +This configuration specifies data for the following properties: + +| Property | Description | +|---------|---------| +| `ClientId` | A unique ID for the client. | +| `ClientName` | The client display name, which is used for logging and the consent screen. | +| `AllowedGrantTypes` | Specifies how a client wants to interact with IdentityServer. For more information see [Configuring the authentication flow](#configuring-the-authentication-flow). | +| `ClientSecrets` | Specifies the client secret credentials that are used when requesting tokens from the token endpoint. | +| `RedirectUris` | Specifies the allowed URIs to which to return tokens or authorization codes. | +| `RequireConsent` | Specifies whether a consent screen is required. | +| `RequirePkce` | Specifies whether clients using an authorization code must send a proof key. | +| `PostLogoutRedirectUris` | Specifies the allowed URIs to redirect to after logout. | +| `AllowedCorsOrigins` | Specifies the origin of the client so that IdentityServer can allow cross-origin calls from the origin. | +| `AllowedScopes` | Specifies the resources the client has access to. By default, a client has no access to any resources. | +| `AllowOfflineAccess` | Specifies whether the client can request refresh tokens. | + +### Configuring the authentication flow + +The authentication flow between a client and IdentityServer can be configured by specifying the grant types in the `Client.AllowedGrantTypes` property. The OpenID Connect and OAuth 2.0 specifications define several authentication flows, including: + +| Authentication Flow | Description | +|---------|---------| +| Implicit | This flow is optimized for browser-based applications and should be used either for user authentication-only, or authentication and access token requests. All tokens are transmitted via the browser, and therefore advanced features like refresh tokens are not permitted. | +| Authorization code | This flow provides the ability to retrieve tokens on a back channel, as opposed to the browser front channel, while also supporting client authentication. | +| Hybrid | This flow is a combination of the implicit and authorization code grant types. The identity token is transmitted via the browser channel and contains the signed protocol response and other artifacts such as the authorization code. After successfully validating the response, the back channel should be used to retrieve the access and refresh token. | + +> [!TIP] +> Consider using the hybrid authentication flow. The hybrid authentication flow mitigates a number of attacks that apply to the browser channel, and is the recommended flow for native applications that want to retrieve access tokens (and possibly refresh tokens). + +For more information about authentication flows, see [Grant Types](https://identityserver4.readthedocs.io/en/latest/topics/grant_types.html) in the IdentityServer 4 documentation. + +### Performing authentication + +For IdentityServer to issue tokens on behalf of a user, the user must sign in to IdentityServer. However, IdentityServer doesn't provide a user interface or database for authentication. Therefore, in the eShopOnContainers reference application, ASP.NET Core Identity is used for this purpose. + +The eShopOnContainers multi-platform app authenticates with IdentityServer with the hybrid authentication flow, which is illustrated in the diagram below. + +![High-level overview of the sign in process.](./media/high-level-overview-sign-in-process.png) + +A sign in request is made to `:5105/connect/authorize`. Following successful authentication, IdentityServer returns an authentication response containing an authorization code and an identity token. The authorization code is sent to `:5105/connect/token`, which responds with access, identity, and refresh tokens. + +The eShopOnContainers multi-platform app signs out of IdentityServer by sending a request to `:5105/connect/endsession` with additional parameters. After sign-out, IdentityServer responds by sending a post-logout redirecting URI back to the multi-platform app. The diagram below illustrates this process. + +![High-level overview of the sign out process.](./media/high-level-overview-sign-out-process.png) + +In the eShopOnContainers multi-platform app, communication with IdentityServer is performed by the `IdentityService` class, which implements the `IIdentityService` interface. This interface specifies that the implementing class must provide `CreateAuthorizationRequest`, `CreateLogoutRequest`, and `GetTokenAsync` methods. + +### Signing-in + +When the user taps the `LOGIN` button on the `LoginView`, the `SignInCommand` in the `LoginViewModel` class is executed, which in turn executes the `SignInAsync` method. The following code example shows this method: + +```csharp +private async Task SignInAsync() +{ + await IsBusyFor( + async () => + { + LoginUrl = _identityService.CreateAuthorizationRequest(); + + IsValid = true; + IsLogin = true; + }); +} +``` + +This method invokes the `CreateAuthorizationRequest` method in the `IdentityService` class, as shown in the following code example: + +```csharp +public string CreateAuthorizationRequest() +{ + // Create URI to authorization endpoint + var authorizeRequest = new AuthorizeRequest(GlobalSetting.Instance.IdentityEndpoint); + // Dictionary with values for the authorize request + var dic = new Dictionary(); + dic.Add("client_id", GlobalSetting.Instance.ClientId); + dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);  + dic.Add("response_type", "code id_token"); + dic.Add("scope", "openid profile basket orders locations marketing offline_access"); + dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback); + dic.Add("nonce", Guid.NewGuid().ToString("N")); + dic.Add("code_challenge", CreateCodeChallenge()); + dic.Add("code_challenge_method", "S256"); + // Add CSRF token to protect against cross-site request forgery attacks. + var currentCSRFToken = Guid.NewGuid().ToString("N"); + dic.Add("state", currentCSRFToken); + + var authorizeUri = authorizeRequest.Create(dic);  + return authorizeUri; +} +``` + +This method creates the URI for IdentityServer's [authorization endpoint](https://identityserver4.readthedocs.io/en/latest/endpoints/authorize.html) with the required parameters. The authorization endpoint is at `/connect/authorize` on port 5105 of the base endpoint exposed as a user setting. For more information about user settings, see [Configuration Management](configuration-management.md). + +> [!NOTE] +> The attack surface of the eShopOnContainers multi-platform app is reduced by implementing the Proof Key for Code Exchange (PKCE) extension to OAuth. PKCE protects the authorization code from being used if it's intercepted. This is achieved by the client generating a secret verifier, a hash of which is passed in the authorization request, and which is presented unhashed when redeeming the authorization code. For more information about PKCE, see [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) on the Internet Engineering Task Force web site. + +The returned URI is stored in the `LoginUrl` property of the `LoginViewModel` class. When the `IsLogin` property becomes true, the `WebView` in the `LoginView` becomes visible. The `WebView` data binds its `Source` property to the `LoginUrl` property of the `LoginViewModel` class and a sign-in request to IdentityServer when the `LoginUrl` property is set to IdentityServer's authorization endpoint. When IdentityServer receives this request and the user isn't authenticated, the `WebView` will be redirected to the configured login page shown in the image below. + +![Login page displayed by the WebView.](./media/login-page-displayed-by-the-webview.png) + +Once login is completed, the `WebView` will be redirected to a return URI. This `WebView` navigation will cause the `NavigateAsync` method in the `LoginViewModel` class to be executed, as shown in the following code example: + +```csharp +private async Task NavigateAsync(string url) +{ + var unescapedUrl = System.Net.WebUtility.UrlDecode(url); + + if (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback, StringComparison.OrdinalIgnoreCase)) + { + _settingsService.AuthAccessToken = string.Empty; + _settingsService.AuthIdToken = string.Empty; + IsLogin = false; + LoginUrl = _identityService.CreateAuthorizationRequest(); + } + else if (unescapedUrl.Contains(GlobalSetting.Instance.Callback, StringComparison.OrdinalIgnoreCase)) + { + var authResponse = new AuthorizeResponse(url); + if (!string.IsNullOrWhiteSpace(authResponse.Code)) + { + var userToken = await _identityService.GetTokenAsync(authResponse.Code); + string accessToken = userToken.AccessToken; + + if (!string.IsNullOrWhiteSpace(accessToken)) + { + _settingsService.AuthAccessToken = accessToken; + _settingsService.AuthIdToken = authResponse.IdentityToken; + await NavigationService.NavigateToAsync("//Main/Catalog"); + } + } + } +} +``` + +This method parses the authentication response contained in the return URI, and provided that a valid authorization code is present, it makes a request to IdentityServer's [token endpoint](https://identityserver4.readthedocs.io/en/latest/endpoints/token.html), passing the authorization code, the PKCE secret verifier, and other required parameters. The token endpoint is at `/connect/token` on port 5105 of the base endpoint exposed as a user setting. For more information about user settings, see [Configuration management](configuration-management.md)). + +> [!TIP] +> Make sure to validate return URIs. Although the eShopOnContainers multi-platform app doesn't validate the return URI, the best practice is to validate that the return URI refers to a known location in order to prevent open-redirect attacks. + +If the token endpoint receives a valid authorization code and PKCE secret verifier, it responds with an access token, identity token, and refresh token. The access token (which allows access to API resources) and identity token are stored as application settings, and page navigation is performed. Therefore, the overall effect in the eShopOnContainers multi-platform app is this: provided that users are able to successfully authenticate with IdentityServer, they are navigated to the `//Main/Catalog` route, which is a `TabbedPage` that displays the `CatalogView` as its selected tab. + +For information about page navigation, see [Navigation](navigation.md). For information about how WebView navigation causes a view model method to be executed, see [Invoking navigation using behaviors](navigation.md#invoking-navigation-using-behaviors). For information about application settings, see [Configuration management](configuration-management.md). + +> [!NOTE] +> The eShopOnContainers also allows a mock sign in when the app is configured to use mock services in the `SettingsView`. In this mode, the app doesn't communicate with IdentityServer, instead allowing the user to sign in using any credentials. + +### Signing-out + +When the user taps the `LOG OUT` button in the `ProfileView`, the `LogoutCommand` in the `ProfileViewModel` class is executed, which executes the `LogoutAsync` method. This method performs page navigation to the `LoginView` page, passing a `LogoutParameter` instance set to `true` as a parameter. + +When a view is created and navigated to, the `InitializeAsync` method of the view's associated view model is executed, which then executes the `Logout` method of the `LoginViewModel` class, which is shown in the following code example: + +```csharp +private void Logout() +{ +    var authIdToken = Settings.AuthIdToken; +    var logoutRequest = _identityService.CreateLogoutRequest(authIdToken); + +    if (!string.IsNullOrEmpty(logoutRequest)) +    { +        // Logout +        LoginUrl = logoutRequest; +    } +     + // Omitted for brevity +} +``` + +This method invokes the `CreateLogoutRequest` method in the `IdentityService` class, passing the identity token retrieved from application settings as a parameter. For more information about application settings, see [Configuration management](configuration-management.md). The following code example shows the `CreateLogoutRequest` method: + +```csharp +public string CreateLogoutRequest(string token) +{ +    // Omitted for brevity + + var (endpoint, callback) = + (GlobalSetting.Instance.LogoutEndpoint, GlobalSetting.Instance.LogoutCallback); + + return $"{endpoint}?id_token_hint={token}&post_logout_redirect_uri={callback}"; +} +``` + +This method creates the URI to IdentityServer's [end session endpoint](https://identityserver4.readthedocs.io/en/latest/endpoints/endsession.html#refendsession) with the required parameters. The end session endpoint is at `/connect/endsession` on port 5105 of the base endpoint exposed as a user setting. + +The returned URI is stored in the `LoginUrl` property of the `LoginViewModel` class. While the `IsLogin` property is true, the `WebView` in the `LoginView` is visible. The `WebView` data binds its `Source` property to the `LoginUrl` property of the `LoginViewModel` class, making a sign-out request to IdentityServer when the `LoginUrl` property is set to IdentityServer's end session endpoint. Sign-out occurs when IdentityServer receives this request, provided the user is signed in. Authentication is tracked with a cookie managed by the cookie authentication middleware from ASP.NET Core. Therefore, signing out of IdentityServer removes the authentication cookie and sends a post-logout redirect URI back to the client. + +The `WebView` will be redirected to the post-logout redirect URI in the multi-platform app. This `WebView` navigation will cause the `NavigateAsync` method in the `LoginViewModel` class to be executed, which is shown in the following code example: + +```csharp +private async Task NavigateAsync(string url) +{ + var unescapedUrl = System.Net.WebUtility.UrlDecode(url); + + if (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback, StringComparison.OrdinalIgnoreCase)) + { + _settingsService.AuthAccessToken = string.Empty; + _settingsService.AuthIdToken = string.Empty; + IsLogin = false; + LoginUrl = _identityService.CreateAuthorizationRequest(); + } + + // Omitted for brevity +} +``` + +This method clears both the identity token and the access token from application settings. It sets the `IsLogin` property to false, which causes the `WebView` on the `LoginView` page to become invisible. Finally, the `LoginUrl` property is set to the URI of IdentityServer's [authorization endpoint](https://identityserver4.readthedocs.io/en/latest/endpoints/authorize.html), with the required parameters, in preparation for the next time the user initiates a sign-in. + +For information about page navigation, see [Navigation](navigation.md). For information about how `WebView` navigation causes a view model method to be executed, see [Invoking navigation using behaviors](navigation.md#invoking-navigation-using-behaviors). For information about application settings, see [Configuration management](configuration-management.md). + +> [!NOTE] +> The eShopOnContainers also allows a mock sign-out when the app is configured to use mock services in the `SettingsView`. In this mode, the app doesn't communicate with IdentityServer, and instead clears any stored tokens from application settings. + +## Authorization + +After authentication, ASP.NET Core web APIs often need to authorize access, which allows a service to make APIs available to some authenticated users but not to all. + +Restricting access to an ASP.NET Core route can be achieved by applying an Authorize attribute to a controller or action, which limits access to the controller or action to authenticated users, as shown in the following code example: + +```csharp +[Authorize] +public sealed class BasketController : Controller +{ +    // Omitted for brevity +} +``` + +If an unauthorized user attempts to access a controller or action marked with the Authorize attribute, the API framework returns a `401 (unauthorized)` HTTP status code. + +> [!NOTE] +> Parameters can be specified on the Authorize attribute to restrict an API to specific users. For more information, see [ASP.NET Core Docs: Authorization](/aspnet/core/security/authorization/introduction). + +IdentityServer can be integrated into the authorization workflow so that the access tokens it provides control authorization. This approach is shown in the diagram below. + +![Authorization by access token.](./media/authorization-by-access-token.png) + +The eShopOnContainers multi-platform app communicates with the identity microservice and requests an access token as part of the authentication process. The access token is then forwarded to the APIs exposed by the ordering and basket microservices as part of the access requests. Access tokens contain information about the client and the user. APIs then use that information to authorize access to their data. For information about how to configure IdentityServer to protect APIs, see [Configuring API resources](#configuring-api-resources). + +## Configuring IdentityServer to perform authorization + +To perform authorization with IdentityServer, its authorization middleware must be added to the web application's HTTP request pipeline. The middleware is added in the `ConfigureAuth` method in the web application's `Startup` class, which is invoked from the `Configure` method and is demonstrated in the following code example from the eShopOnContainers reference application: + +```csharp +protected virtual void ConfigureAuth(IApplicationBuilder app) +{ + var identityUrl = Configuration.GetValue("IdentityUrl"); + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = identityUrl.ToString(), + ScopeName = "basket", + RequireHttpsMetadata = false, + }); +} +``` + +This method ensures that the API can only be accessed with a valid access token. The middleware validates the incoming token to ensure that it's sent from a trusted issuer and validates that the token is valid to be used with the API that receives it. Therefore, browsing to the ordering or basket controller will return a `401 (unauthorized)` HTTP status code, indicating that an access token is required. + +> [!NOTE] +> IdentityServer's authorization middleware must be added to the web application's HTTP request pipeline before adding MVC with `app.UseMvc()` or `app.UseMvcWithDefaultRoute()`. + +## Making access requests to APIs + +When making requests to the ordering and basket microservices, the access token obtained from IdentityServer during the authentication process must be included in the request, as shown in the following code example: + +```csharp +var authToken = Settings.AuthAccessToken; +Order = await _ordersService.GetOrderAsync(order.OrderNumber, authToken); +``` + +The access token is stored as an application setting and is retrieved from platform-specific storage and included in the call to the `GetOrderAsync` method in the `OrderService` class. + +Similarly, the access token must be included when sending data to an IdentityServer protected API, as shown in the following code example: + +```csharp +var authToken = Settings.AuthAccessToken; +await _basketService.UpdateBasketAsync( + new CustomerBasket + { + BuyerId = userInfo.UserId,  + Items = BasketItems.ToList() + },  + authToken); +``` + +The access token is retrieved from settings and included in the call to the `UpdateBasketAsync` method in the `BasketService` class. + +The `RequestProvider` class in the eShopOnContainers multi-platform app uses the `HttpClient` class to make requests to the RESTful APIs exposed by the eShopOnContainers reference application. When making requests to the ordering and basket APIs, which require authorization, a valid access token must be included with the request. This is achieved by adding the access token to the headers of the HttpClient instance, as demonstrated in the following code example: + +```csharp +httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); +``` + +The `DefaultRequestHeaders` property of the `HttpClient` class exposes the headers that are sent with each request, and the access token is added to the `Authorization` header prefixed with the string `Bearer`. When the request is sent to a RESTful API, the value of the `Authorization` header is extracted and validated to ensure that it's sent from a trusted issuer and used to determine whether the user has permission to invoke the API that receives it. + +For more information about how the eShopOnContainers multi-platform app makes web requests, see [Accessing remote data](accessing-remote-data.md). + +## Summary + +There are many approaches to integrating authentication and authorization into a .NET MAUI app that communicates with an ASP.NET web application. The eShopOnContainers multi-platform app performs authentication and authorization with a containerized identity microservice that uses IdentityServer 4. IdentityServer is an open source OpenID Connect and OAuth 2.0 framework for ASP.NET Core that integrates with ASP.NET Core Identity to perform bearer token authentication. + +The multi-platform app requests security tokens from IdentityServer to authenticate a user or access a resource. When accessing a resource, an access token must be included in the request to APIs that require authorization. IdentityServer's middleware validates incoming access tokens to ensure that they are sent from a trusted issuer and that they are valid to be used with the API that receives them. diff --git a/docs/architecture/maui/communicating-between-components.md b/docs/architecture/maui/communicating-between-components.md new file mode 100644 index 0000000000000..3a77f8e7b9b3e --- /dev/null +++ b/docs/architecture/maui/communicating-between-components.md @@ -0,0 +1,115 @@ +--- +title: Communicating Between Components +description: Providing loosely-coupled messaging for a .NET MAUI application +author: michaelstonis +no-loc: [MAUI] +ms.date: 07/02/2022 +--- + +# Communicating between loosely coupled components + +[!INCLUDE [download-alert](includes/download-alert.md)] + +The publish-subscribe pattern is a messaging pattern in which publishers send messages without having knowledge of any receivers, known as subscribers. Similarly, subscribers listen for specific messages, without having knowledge of any publishers. + +Events in .NET implement the publish-subscribe pattern, and are the most simple and straightforward approach for a communication layer between components if loose coupling is not required, such as a control and the page that contains it. However, the publisher and subscriber lifetimes are coupled by object references to each other, and the subscriber type must have a reference to the publisher type. This can create memory management issues, especially when there are short lived objects that subscribe to an event of a static or long-lived object. If the event handler isn't removed, the subscriber will be kept alive by the reference to it in the publisher, and this will prevent or delay the garbage collection of the subscriber. + +## Introduction to MessagingCenter + +The .NET MAUI `MessagingCenter` class implements the publish-subscribe pattern, allowing message-based communication between components that are inconvenient to link by object and type references. This mechanism allows publishers and subscribers to communicate without having a direct reference to each other, helping to reduce dependencies between components, while also allowing components to be independently developed and tested. + +The `MessagingCenter` class provides multicast publish-subscribe functionality. This means that there can be multiple publishers that publish a single message, and there can be multiple subscribers listening for the same message. The image below illustrates this relationship: + +![Multicast publish-subscribe functionality.](./media/messaging-center.png) + +Publishers send messages using the `MessagingCenter.Send` method, while subscribers listen for messages using the `MessagingCenter.Subscribe` method. In addition, subscribers can also unsubscribe from message subscriptions, if required, with the `MessagingCenter.Unsubscribe` method. + +Internally, the `MessagingCenter` class uses weak references. This means that it will not keep objects alive, and will allow them to be garbage collected. Therefore, it should only be necessary to unsubscribe from a message when a class no longer wishes to receive the message. + +The eShopOnContainers multi-platform app uses the `MessagingCenter` class to communicate between loosely coupled components. The app defines a single message. The `AddProduct` message is published by the `CatalogViewModel` class when an item is added to the shopping basket. In return, the `CatalogView` class subscribes to the message and uses this to highlight the product adds with an animation in response. + +> [!NOTE] +> While the `MessagingCenter` class permits communication between loosely-coupled classes, it does not offer the only architectural solution to this issue. For example, communication between a view model and a view can also be achieved by the binding engine and through property change notifications. In addition, communication between two view models can also be achieved by passing data during navigation. + +In the eShopOnContainers multi-platform app, `MessagingCenter` is used to update in the UI in response to an action occurring in another class. Therefore, messages are published on the UI thread, with subscribers receiving the message on the same thread. + +> [!TIP] +> Marshal to the UI or main thread when performing UI updates. If updates to user interfaces are not made on this thread, it can cause the application to crash or become unstable. + +If a message that's sent from a background thread is required to update the UI, process the message on the UI thread in the subscriber by invoking the `MainThread.BeginInvokeOnMainThread` method. + +For more information about `MessagingCenter`, see [MessagingCenter](/dotnet/maui/fundamentals/messagingcenter) on the Microsoft Developer Center. + +## Defining a message + +`MessagingCenter` messages are strings that are used to identify messages. The following code example shows the messages defined within the eShopOnContainers multi-platform app: + +```csharp +public static class MessengerKeys +{ +    // Add product to basket +    public const string AddProduct = nameof(AddProduct); +} +``` + +In this example, messages are defined using constants. The advantage of this approach is that it provides compile-time type safety and refactoring support. + +## Publishing a message + +Publishers notify subscribers of a message with one of the `MessagingCenter.Send` overloads. The following code example demonstrates publishing the `AddProduct` message: + +```csharp +MessagingCenter.Send(this, MessengerKeys.AddProduct); +``` + +In this example, the Send method specifies three arguments: + +- The first argument specifies the sender class. The sender class must be specified by any subscribers who wish to receive the message. +- The second argument specifies the message. + +If needed, a third argument can specify the payload data to be sent to the subscriber. This payload can be any `object` type. It will be important that the subscriber of the message knows the expected payload type. + +The Send method will publish the message, and its payload data, using a fire-and-forget approach. Therefore, the message is sent even if there are no subscribers registered to receive the message. In this situation, the sent message is ignored. + +> [!NOTE] +> The `MessagingCenter.Send` method can use generic parameters to control how messages are delivered. Therefore, multiple messages that share a message identity but send different payload data types can be received by different subscribers. + +## Subscribing to a message + +Subscribers can register to receive a message using one of the `MessagingCenter.Subscribe` overloads. The following code example demonstrates how the eShopOnContainers multi-platform app subscribes to, and processes, the `AddProduct` message: + +```csharp +MessagingCenter.Subscribe( + this, + MessengerKeys.AddProduct, + _ => + { + MainThread.BeginInvokeOnMainThread( + async () => + { + await badge.ScaleTo(1.2); + await badge.ScaleTo(1.0); + }); + }); +``` + +In the preceding example, the Subscribe method subscribes to the `AddProduct` message, and executes a callback delegate in response to receiving the message. This callback delegate, specified as a lambda expression, executes code that updates the UI. + +If payload data is supplied, don't attempt to modify the payload data from within a callback delegate because several threads could be accessing the received data simultaneously. In this scenario, the payload data should be immutable to avoid concurrency errors. + +A subscriber might not need to handle every instance of a published message, and this can be controlled by the generic type arguments that are specified on the Subscribe method. In this example, the subscriber will only receive `AddProduct` messages that are sent from the `CatalogViewModel` class. + +## Unsubscribing from a message + +Subscribers can unsubscribe from messages they no longer want to receive. This is achieved with one of the `MessagingCenter`.Unsubscribe overloads, as demonstrated in the following code example: + +```csharp +MessagingCenter.Unsubscribe( + this, MessengerKeys.AddProduct); +``` + +In this example, the Unsubscribe method syntax reflects the type arguments specified when subscribing to receive the AddProduct message. + +## Summary + +The .NET MAUI `MessagingCenter` class implements the publish-subscribe pattern, allowing message-based communication between components that are inconvenient to link by object and type references. This mechanism allows publishers and subscribers to communicate without having a reference to each other, helping to reduce dependencies between components, while also allowing components to be independently developed and tested. diff --git a/docs/architecture/maui/configuration-management.md b/docs/architecture/maui/configuration-management.md new file mode 100644 index 0000000000000..5d06355a2c5a4 --- /dev/null +++ b/docs/architecture/maui/configuration-management.md @@ -0,0 +1,136 @@ +--- +title: Configuration Management +description: Managing persistent configurations in .NET MAUI +author: michaelstonis +no-loc: [MAUI] +ms.date: 06/28/2022 +--- + +# Configuration management + +[!INCLUDE [download-alert](includes/download-alert.md)] + +Settings allow the separation of data that configures the behavior of an app from the code, allowing the behavior to be changed without rebuilding the app. There are two types of settings: app settings and user settings. + +App settings are data that an app creates and manages. It can include data such as fixed web service endpoints, API keys, and runtime state. App settings are tied to core functionality and are only meaningful to that app. + +User settings are the customizable settings of an app that affect the app's behavior and don't require frequent re-adjustment. For example, an app might let the user specify where to retrieve data and how to display it on the screen. + +## Creating a Settings Interface + +While the preferences manager can be used directly in your application, it does come with the drawback of making your application tightly coupled to the preferences manager implementation. This coupling means that creating unit tests or extending the functionality of preferences management will be limited since your application will not have a direct way to intercept the behavior. To address this concern, an interface can be created to work as a proxy for preferences management. The interface will allow us to supply an implementation that fits our needs. For example, when writing a unit test, we may want to set specific settings, and the interface will give us an easy way to consistently set this data for the test. The following code example shows the `ISettingsService` interface in the eShopOnContainers multi-platform app: + +```csharp +namespace eShopOnContainers.Services.Settings; + +public interface ISettingsService +{ + string AuthAccessToken { get; set; } + string AuthIdToken { get; set; } + bool UseMocks { get; set; } + string IdentityEndpointBase { get; set; } + string GatewayShoppingEndpointBase { get; set; } + string GatewayMarketingEndpointBase { get; set; } + bool UseFakeLocation { get; set; } + string Latitude { get; set; } + string Longitude { get; set; } + bool AllowGpsLocation { get; set; } +} +``` + +## Adding Settings + +.NET MAUI includes a preferences manager that provides a way to store runtime settings for a user. This feature can be accessed from anywhere within your application using the `Microsoft.Maui.Storage.Preferences` class. The preferences manager provides a consistent, type-safe, cross-platform approach for persisting and retrieving app and user settings, while using the native settings management provided by each platform. In addition, it's straightforward to use data binding to access settings data exposed by the library. For more information, see the [Preferences](/dotnet/maui/platform-integration/storage/preferences) on the Microsoft Developer Center. + +> [!TIP] +> Preferences is meant for storing relatively small data. If you need to store larger or more complex data, consider using a local database or filesystem to store the data. + +Our application will use the `Preferences` class need to implement the `ISettingsService` interface. The code below shows how the eShopOnContainers multi-platform app's `SettingsService` implements the `AuthTokenAccess` and `UseMocks` properties: + +```csharp +public sealed class SettingsService : ISettingsService +{ + private const string AccessToken = "access_token"; + private const string AccessTokenDefault = string.Empty; + + private const string IdUseMocks = "use_mocks"; + private const bool UseMocksDefault = true; + + public string AuthAccessToken + { + get => Preferences.Get(AccessToken, AccessTokenDefault); + set => Preferences.Set(AccessToken, value); + } + + public bool UseMocks + { + get => Preferences.Get(IdUseMocks, UseMocksDefault); + set => Preferences.Set(IdUseMocks, value); + } +} +``` + +Each setting consists of a private key, a private default value, and a public property. The key is always a const string that defines a unique name, with the default value for the setting being a static read-only or constant value of the required type. Providing a default value ensures that a valid value is available if an unset setting is retrieved. This service implementation can be provided via dependency injection to our application for use in view-models or other services throughout the application. + +## Data binding to user settings + +In the eShopOnContainers multi-platform app, the `SettingsView` exposes multiple settings the user can configure at runtime. These settings include allowing configuration of whether the app should retrieve data from microservices deployed as Docker containers or whether the app should retrieve data from mock services that don't require an internet connection. When retrieving data from containerized microservices, a base endpoint URL for the microservices must be specified. The image below shows the SettingsView when the user has chosen to retrieve data from containerized microservices. + +![User settings exposed by the eShopOnContainers multi-platform app.](./media/endpoint_settings.png) + +Data binding can be used to retrieve and set settings exposed by the `ISettingService` interface. This is achieved by controls on the view binding to view model properties that in turn access properties in the `ISettingService` interface and raising a property changed notification if the value has changed. + +The following code example shows the `Entry` control from the `SettingsView` that allows the user to enter a base identity endpoint URL for the containerized microservices: + +```xml + +``` + +This `Entry` control binds to the `IdentityEndpoint` property of the `SettingsViewModel` class, using a two-way binding. The following code example shows the `IdentityEndpoint` property: + +```csharp +private readonly ISettingsService _settingsService; + +private string _identityEndpoint; + +public SettingsViewModel( + ILocationService locationService, IAppEnvironmentService appEnvironmentService, + IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService) + : base(dialogService, navigationService, settingsService) +{ + _settingsService = settingsService; + + _identityEndpoint = _settingsService.IdentityEndpointBase; +} + +public string IdentityEndpoint +{ + get => _identityEndpoint; + set + { + SetProperty(ref _identityEndpoint, value); + + if (!string.IsNullOrWhiteSpace(value)) + { + UpdateIdentityEndpoint(); + } + } +} +``` + +When the `IdentityEndpoint` property is set, the `UpdateIdentityEndpoint` method is called, provided that the supplied value is valid. The following code example shows the `UpdateIdentityEndpoint` method: + +```csharp +private void UpdateIdentityEndpoint() +{ + _settingsService.IdentityEndpointBase = _identityEndpoint; +} +``` + +This method updates the `IdentityEndpointBase` property in the `ISettingService` interface implementation with the base endpoint URL value entered by the user. If the `SettingsService` class is provided as the implementation for `_settingsService`, the value will persist to platform-specific storage. + +## Summary + +Settings allow the separation of data that configures the behavior of an app from the code, allowing the behavior to be changed without rebuilding the app. App settings are data that an app creates and manages, and user settings are the customizable settings of an app that affect the app's behavior and don't require frequent re-adjustment. + +The `Microsoft.Maui.Storage.Preferences` class provides a consistent, type-safe, cross-platform approach for persisting and retrieving app and user settings. diff --git a/docs/architecture/maui/dependency-injection.md b/docs/architecture/maui/dependency-injection.md new file mode 100644 index 0000000000000..77f440e33152d --- /dev/null +++ b/docs/architecture/maui/dependency-injection.md @@ -0,0 +1,191 @@ +--- +title: Dependency Injection +description: Patterns for building flexible, decoupled, and testable applications +author: michaelstonis +no-loc: [MAUI] +ms.date: 07/10/2022 +--- + +# Dependency injection + +[!INCLUDE [download-alert](includes/download-alert.md)] + +Typically, a class constructor is invoked when instantiating an object, and any values that the object needs are passed as arguments to the constructor. This is an example of dependency injection known as *constructor injection*. The dependencies the object needs are injected into the constructor. + +By specifying dependencies as interface types, dependency injection enables decoupling the concrete types from the code that depends on these types. It generally uses a container that holds a list of registrations and mappings between interfaces and abstract types, and the concrete types that implement or extend these types. + +There are also other types of dependency injection, such as *property setter injection* and *method call injection*, but they are less commonly seen. Therefore, this chapter will focus solely on performing constructor injection with a dependency injection container. + +## Introduction to dependency injection + +Dependency injection is a specialized version of the Inversion of Control (IoC) pattern, where the concern being inverted is the process of obtaining the required dependency. With dependency injection, another class is responsible for injecting dependencies into an object at runtime. The following code example shows how the `ProfileViewModel` class is structured when using dependency injection: + +```csharp +private readonly ISettingsService _settingsService; +private readonly IAppEnvironmentService _appEnvironmentService; + +public ProfileViewModel( + IAppEnvironmentService appEnvironmentService, + IDialogService dialogService, + INavigationService navigationService, + ISettingsService settingsService) + : base(dialogService, navigationService, settingsService) +{ + _appEnvironmentService = appEnvironmentService; + _settingsService = settingsService; + + // Omitted for brevity +} +``` + +The `ProfileViewModel` constructor receives multiple interface object instances as arguments injected by another class. The only dependency in the `ProfileViewModel` class is on the interface types. Therefore, the `ProfileViewModel` class doesn't have any knowledge of the class that's responsible for instantiating the interface objects. The class that's responsible for instantiating the interface objects, and inserting it into the `ProfileViewModel` class, is known as the *dependency injection container*. + +Dependency injection containers reduce the coupling between objects by providing a facility to instantiate class instances and manage their lifetime based on the configuration of the container. During object creation, the container injects any dependencies that the object requires into it. If those dependencies have not yet been created, the container creates and resolves their dependencies first. + +There are several advantages to using a dependency injection container: + +- A container removes the need for a class to locate its dependencies and manage its lifetimes. +- A container allows the mapping of implemented dependencies without affecting the class. +- A container facilitates testability by allowing dependencies to be mocked. +- A container increases maintainability by allowing new classes to be easily added to the app. + +In the context of a .NET MAUI app that uses MVVM, a dependency injection container will typically be used for registering and resolving views, registering and resolving view models, and for registering services and injecting them into view models. + +There are many dependency injection containers available in .NET; the eShopOnContainers multi-platform app uses `Microsoft.Extensions.DependencyInjection` to manage the instantiation of views, view models, and service classes in the app. `Microsoft.Extensions.DependencyInjection` facilitates building loosely coupled apps, and provides all of the features commonly found in dependency injection containers, including methods to register type mappings and object instances, resolve objects, manage object lifetimes, and inject dependent objects into constructors of objects that it resolves. For more information about `Microsoft.Extensions.DependencyInjection`, see [Dependency injection in .NET](../../core/extensions/dependency-injection.md). + +In .NET MAUI, the `MauiProgram` class will call into the `CreateMauiApp` method to create a `MauiAppBuilder` object. The `MauiAppBuilder` object has a `Services` property of type `IServiceCollection`, which provides a place to register our components, such as views, view models, and services for dependency injection. Any components registered with the `Services` property will be provided to the dependency injection container when the `MauiAppBuilder.Build` method is called. + +At runtime, the container must know which implementation of the services are being requested in order to instantiate them for the requested objects. In the eShopOnContainers multi-platform app, the `IAppEnvironmentService`, `IDialogService` , `INavigationService`, and `ISettingsService` interfaces need to be resolved before it can instantiate a `ProfileViewModel` object. This involves the container performing the following actions: + +- Deciding how to instantiate an object that implements the interface. This is known as *registration*. +- Instantiating the object that implements the required interface and the `ProfileViewModel` object. This is known as *resolution*. + +Eventually, the app will finish using the `ProfileViewModel` object, and it will become available for garbage collection. At this point, the garbage collector should dispose of any short-lived interface implementations if other classes do not share the same instance. + +## Registration + +Before dependencies can be injected into an object, the types of the dependencies must first be registered with the container. Registering a type involves passing the container an interface and a concrete type that implements the interface. + +There are two ways of registering types and objects in the container through code: + +- Register a type or mapping with the container. This is known as transient registration. When required, the container will build an instance of the specified type. +- Register an existing object in the container as a singleton. When required, the container will return a reference to the existing object. + +> [!NOTE] +> Dependency injection containers are not always suitable. Dependency injection introduces additional complexity and requirements that might not be appropriate or useful to small apps. If a class does not have any dependencies, or is not a dependency for other types, it might not make sense to put it in the container. In addition, if a class has a single set of dependencies that are integral to the type and will never change, it might not make sense to put it in the container. + +The registration of types requiring dependency injection should be performed in a single method in an app. This method should be invoked early in the app's lifecycle to ensure it is aware of the dependencies between its classes. The eShopOnContainers multi-platform app performs this the `MauiProgram.CreateMauiApp` method. The following code example shows how the eShopOnContainers multi-platform app declares the `CreateMauiApp` in the `MauiProgram` class: + +```csharp +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + => MauiApp.CreateBuilder() + .UseMauiApp() + // Omitted for brevity + .RegisterAppServices() + .RegisterViewModels() + .RegisterViews() + .Build(); +} +``` + +The `MauiApp.CreateBuilder` method creates a `MauiAppBuilder` object that we can use to register our dependencies. Many dependencies in the eShopOnContainers multi-platform app need to be registered, so the extension methods `RegisterAppServices`, `RegisterViewModels`, and `RegisterViews` were created to help provide an organized and maintainable registration workflow. The following code shows the `RegisterViewModels` method: + +```csharp +public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder) +{ + mauiAppBuilder.Services.AddSingleton(); + mauiAppBuilder.Services.AddSingleton(); + mauiAppBuilder.Services.AddSingleton(); + mauiAppBuilder.Services.AddSingleton(); + mauiAppBuilder.Services.AddSingleton(); + + mauiAppBuilder.Services.AddTransient(); + mauiAppBuilder.Services.AddTransient(); + mauiAppBuilder.Services.AddTransient(); + mauiAppBuilder.Services.AddTransient(); + mauiAppBuilder.Services.AddTransient(); + + return mauiAppBuilder; +} +``` + +This method receives an instance of `MauiAppBuilder`, and we can use the `Services` property to register our view models. Depending on the needs of your application, you may need to add services with different lifetimes. The following table provides information on when you may want to choose these different registration lifetimes: + +| Method | Description | +|---------|---------| +| `AddSingleton` | Will create a single instance of the object which will be remain for the lifetime of the application. | +| `AddTransient` | Will create a new instance of the object when requested during resolution. Transient objects do not have a pre-defined lifetime, but will typically follow the lifetime of their host. | + +> [!NOTE] +> The view models do not inherit from an interface, so they only need their concrete type provided to the `AddSingleton` and `AddTransient` methods. + +The `CatalogViewModel` is used near the application's root and should always be available, so registering it with `AddSingleton` is beneficial. Other view models, such as `CheckoutViewModel` and `OrderDetailViewModel` are situationally navigated to or are used later in the application. Suppose you know that you have a component that may not always be used. In that case, if it is memory or computationally intensive or requires just-in-time data, it may be a better candidate for `AddTransient` registration. + +Another common way to add services is using the `AddSingleton` and `AddTransient` methods. These methods take two input types: the interface definition and the concrete implementation. This type of registration is best for cases where you are implementing services based on interfaces. In the code example below, we register our `ISettingsService` interface using the `SettingsService` implementation: + +```csharp +public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder) +{ + mauiAppBuilder.Services.AddSingleton(); + // Omitted for brevity... +} +``` + +Once all services have been registered, the `MauiAppBuilder.Build` method should be called to create our `MauiApp` and populate our dependency injection container with all the registered services. + +> [!IMPORTANT] +> Once the `Build` method has been called, the services registered with the dependency injection container will be immutable and no longer can be updated or modified. + +## Resolution + +After a type is registered, it can be resolved or injected as a dependency. When a type is being resolved, and the container needs to create a new instance, it injects any dependencies into the instance. + +Generally, when a type is resolved, one of three things happens: + +1. If the type hasn't been registered, the container throws an exception. +2. If the type has been registered as a singleton, the container returns the singleton instance. If this is the first time the type is called for, the container creates it if required and maintains a reference to it. +3. If the type has been registered as transient, the container returns a new instance and doesn't maintain a reference to it. + +.NET MAUI offers a number of ways to resolve registered components based on your needs. The most direct way to gain acces to the dependency injection container is from an `Element` using the `Handler.MauiContext.Services`. An example of this is shown below: + +```csharp +var settingsService = this.Handler.MauiContext.Services.GetServices(); +``` + +This can be helpful if you need to resolve a service from within an `Element` or from outside of the constructor of your `Element`. + +> [!CAUTION] +> There is a possibility that the `Handler` property of your `Element` may be null, so be aware that you may need to handle those situations. For more information, please refer to [Handler lifecycle](/dotnet/maui/user-interface/handlers/customize#handler-lifecycle) on the Microsoft Documentation Center. + +If using the `Shell` control for .NET MAUI, it will implicitly call into the dependency injection container to create our objects during navigation. When setting up our `Shell` control, the `Routing.RegisterRoute` method will tie a route path to a `View` as shown in the example below: + +```csharp +Routing.RegisterRoute("Filter", typeof(FiltersView)); +``` + +During `Shell` navigation, it will look for registrations of the `FiltersView`, and if any are found, it will create that view and inject any dependencies into the constructor. As shown in the code example below, the `CatalogViewModel` will be injected into the `FiltersView`: + +```csharp +namespace eShopOnContainers.Views; + +public partial class FiltersView : ContentPage +{ + public FiltersView(CatalogViewModel viewModel) + { + BindingContext = viewModel; + + InitializeComponent(); + } +} +``` + +> [!TIP] +> The dependency injection container is great for creating view model instances. If a view model has dependencies, it will handle the creation and injection of any required services. Just make sure that you register your view models and any dependencies that they may have with the `CreateMauiApp` method in the `MauiProgram` class. + +## Summary + +Dependency injection enables the decoupling of concrete types from the code that depends on these types. It typically uses a container that holds a list of registrations and mappings between interfaces and abstract types, and the concrete types that implement or extend these types. + +`Microsoft.Extensions.DependencyInjection` facilitates building loosely coupled apps and provides all of the features commonly found in dependency injection containers, including methods to register type mappings and object instances, resolve objects, manage object lifetimes, and inject dependent objects into constructors of objects it resolves. diff --git a/docs/architecture/maui/includes/download-alert.md b/docs/architecture/maui/includes/download-alert.md new file mode 100644 index 0000000000000..6769266d60d43 --- /dev/null +++ b/docs/architecture/maui/includes/download-alert.md @@ -0,0 +1,19 @@ +--- +author: IEvangelist +ms.author: dapine +ms.date: 08/24/2022 +ms.topic: include +--- + +> [!TIP] +> :::row::: +> :::column span="3"::: +> This content is an excerpt from the eBook, Enterprise Application Patterns Using .NET MAUI, available on [.NET Docs](/dotnet/architecture/maui) or as a free downloadable PDF that can be read offline. +> +> > [!div class="nextstepaction"] +> > [Download PDF](https://dotnet.microsoft.com/download/e-book/maui/pdf) +> :::column-end::: +> :::column::: +> :::image type="content" source="../media/cover-thumbnail.png" alt-text="Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail."::: +> :::column-end::: +> :::row-end::: diff --git a/docs/architecture/maui/index.md b/docs/architecture/maui/index.md new file mode 100644 index 0000000000000..77a6085e7d41b --- /dev/null +++ b/docs/architecture/maui/index.md @@ -0,0 +1,80 @@ +--- +title: Enterprise Application Patterns Using .NET MAUI +description: Learn how to design adaptable, maintainable, and testable MAUI applications. +author: michaelstonis +no-loc: [MAUI] +ms.date: 07/12/2022 +--- + +# Enterprise Application Patterns Using .NET MAUI + +![cover image Enterprise Application Patterns Using .NET MAUI](./media/enterprise-app-patterns-ebook.png) + +DOWNLOAD available at: + +**EDITION v1.0** + +PUBLISHED BY + +Microsoft Developer Division, .NET, and Visual Studio product teams + +A division of Microsoft Corporation + +One Microsoft Way + +Redmond, Washington 98052-6399 + +Copyright © 2022 by Microsoft Corporation + +All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher. + +This book is provided "as-is" and expresses the author's views and opinions. The views, opinions, and information expressed in this book, including URL and other Internet website references, may change without notice. + +Some examples depicted herein are provided for illustration only and are fictitious. No real association or connection is intended or should be inferred. + +Microsoft and the trademarks listed at on the "Trademarks" webpage are trademarks of the Microsoft group of companies. + +Mac and macOS are trademarks of Apple Inc. + +All other marks and logos are property of their respective owners. + +Authors: + +> **[Michael Stonis](https://github.com/michaelstonis)**, Mobile Software Architect, [Eight-Bot](https://eightbot.com) + +Reviewers: + +> **[James Montemagno](https://github.com/jamesmontemagno)**, Principal Lead Program Manager, Microsoft Corp. + +> **[David Pine](https://github.com/IEvangelist)**, Developer Relations, Microsoft Corp. + +## Acknowledgments + +This book originated from the excellent Enterprise Application Patterns using Xamarin.Forms eBook by [David Britch](https://github.com/davidbritch) and [Javier Suarez Ruiz](https://github.com/jsuarezruiz). Without their hard work, detailed information, and excellent examples, this book would not be possible. + +## Introduction + +Enterprise applications face a number of difficult problems to solve including ever changing business requirements, the need for quick turn around time, support for multiple platforms, and integration with multiple systems. Due to the varying nature of these problems, it's important that our application's architecture allows it to be modular, modifiable and extensible over time. + +This book takes provides real world solutions for addressing these issues when building an enterprise application using .NET MAUI. This book uses a pre-built .NET MAUI application that serves as the front-end of an online eCommerce application as a reference and a guide for common enterprise design patterns. This book covers topics such as the MVVM pattern, dependency injection, navigation, configuration, the loose-coupling of components and additional enterprise concerns. The content of this book is helpful for anyone looking to build a new application for this business or looking to solve the problems of applications that evolve over time. + +## Who should use the book + +This book is for .NET MAUI developers that are already familiar with the framework, but that are looking for guidance on architecture and implementation when building enterprise applications. This book can help developers solve common problems using tried and true patterns. + +## How to use the book + +This book focuses on building cross-platform enterprise apps using .NET MAUI. As such, it should be read in its entirety to provide a foundation of understanding such apps and their technical considerations. The book, along with its sample app, can also serve as a starting point or reference for creating a new enterprise app. Use the associated sample app as a template for the new app, or to see how to organize an app's component parts. Then, refer back to this guide for architectural guidance. You can find the sample app on [GitHub](https://github.com/dotnet-architecture/eshop-mobile-client). + +## What this book doesn't cover + +This book is aimed at readers who are already familiar with .NET MAUI. It does cover some concepts of .NET MAUI to help better illustrate the topic, but it does not cover most controls and concepts in any detail. For general guidance on building a new .NET MAUI app, please refer to the [Building your first app](/dotnet/maui/get-started/first-app) guide in the .NET MAUI Documentation + +### Additional resources + +For official .NET MAUI content, see [.NET MAUI docs](/dotnet/maui). .NET MAUI is developed as an open-source project and is available on GitHub at [dotnet/maui](https://github.com/dotnet/maui). For code samples developed with .NET MAUI, see the [dotnet/maui-samples](https://github.com/dotnet/maui-samples) repo. + +[!INCLUDE [feedback](../includes/feedback.md)] + +>[!div class="step-by-step"] +>[Next](introduction.md) diff --git a/docs/architecture/maui/introduction.md b/docs/architecture/maui/introduction.md new file mode 100644 index 0000000000000..27ff936acb87e --- /dev/null +++ b/docs/architecture/maui/introduction.md @@ -0,0 +1,139 @@ +--- +title: Introduction to .NET MAUI +description: Introduction to enterprise architecture for .NET MAUI +author: michaelstonis +no-loc: [MAUI] +ms.date: 06/18/2022 +--- + +# Introduction to .NET MAUI + +[!INCLUDE [download-alert](includes/download-alert.md)] + +Regardless of platform, developers of enterprise apps face several challenges: + +- App requirements that can change over time. +- New business opportunities and challenges. +- Ongoing feedback during development that can significantly affect the scope and requirements of the app. + +With these in mind, it's important to build apps that can be easily modified or extended over time. Designing for such adaptability can be difficult as it requires an architecture that allows individual parts of the app to be independently developed and tested in isolation without affecting the rest of the app. + +Many enterprise apps are sufficiently complex to require more than one developer. It can be a significant challenge to decide how to design an app so that multiple developers can work effectively on different pieces of the app independently, while ensuring that the pieces come together seamlessly when integrated into the app. + +The traditional approach to designing and building an app results in what is referred to as a *monolithic* app, where components are tightly coupled with no clear separation between them. Typically, this monolithic approach leads to apps that are difficult and inefficient to maintain, because it can be difficult to resolve bugs without breaking other components in the app, and it can be difficult to add new features or to replace existing features. + +An effective remedy for these challenges is to partition an app into discrete, loosely coupled components that can be easily integrated together into an app. Such an approach offers several benefits: + +- It allows individual functionality to be developed, tested, extended, and maintained by different individuals or teams. +- It promotes reuse and a clean separation of concerns between the app's horizontal capabilities, such as authentication and data access, and the vertical capabilities, such as app specific business functionality. This allows the dependencies and interactions between app components to be more easily managed. +- It helps maintain a separation of roles by allowing different individuals, or teams, to focus on a specific task or piece of functionality according to their expertise. In particular, it provides a cleaner separation between the user interface and the app's business logic. + +However, there are many issues that must be resolved when partitioning an app into discrete, loosely coupled components. These include: + +- Deciding how to provide a clean separation of concerns between the user interface controls and their logic. One of the most important decisions when creating a .NET MAUI enterprise app is whether to place business logic in code-behind files, or whether to create a clean separation of concerns between the user interface controls and their logic, in order to make the app more maintainable and testable. For more information, see [Model-View-ViewModel](mvvm.md). +- Determining whether to use a dependency injection container. Dependency injection containers reduce the dependency coupling between objects by providing a facility to construct instances of classes with their dependencies injected, and manage their lifetime based on the configuration of the container. For more information, see [Dependency injection](dependency-injection.md). +- Choosing between platform provided eventing and loosely coupled message-based communication between components that are inconvenient to link by object and type references. For more information, see Introduction to [Communicating between loosely coupled components](communicating-between-components.md). +- Deciding how to navigate between pages, including how to invoke navigation, and where navigation logic should reside. For more information, see [Navigation](navigation.md). +- Determining how to validate user input for correctness. The decision must include how to validate user input, and how to notify the user about validation errors. For more information, see [Validation](validation.md). +- Deciding how to perform authentication, and how to protect resources with authorization. For more information, see [Authentication and authorization](authentication-and-authorization.md). +- Determining how to access remote data from web services, including how to reliably retrieve data, and how to cache data. For more information, see [Accessing remote data](accessing-remote-data.md). +- Deciding how to test the app. For more information, see [Unit testing](unit-testing.md). + +This guide provides guidance on these issues, and focuses on the core patterns and architecture for building a cross-platform enterprise app using .NET MAUI. The guidance aims to help to produce adaptable, maintainable, and testable code, by addressing common .NET MAUI enterprise app development scenarios, and by separating the concerns of presentation, presentation logic, and entities through support for the Model-View-ViewModel (MVVM) pattern. + +## Sample application + +This guide includes a sample application, eShopOnContainers, that's an online store that includes the following functionality: + +- Authenticating and authorizing against a backend service. +- Browsing a catalog of shirts, coffee mugs, and other marketing items. +- Filtering the catalog. +- Ordering items from the catalog. +- Viewing the user's order history. +- Configuration of settings. + +## Sample application architecture + +Below is a high-level overview of the architecture of the sample application. + +![eShopOnContainers high-level architecture](./media/high-level-architecture-diagram.png) + +The sample application ships with three client apps: + +- An MVC application developed with ASP.NET Core. +- A Single Page Application (SPA) developed with Angular 2 and Typescript. This approach for web applications avoids performing a round-trip to the server with each operation. +- A multi-platform app developed with .NET MAUI, which supports iOS, Android, macOS via Mac Catalyst, and Windows 10/11. + +For information about the web applications, see [Architecting and Developing Modern Web Applications with ASP.NET Core and Microsoft Azure](https://aka.ms/WebAppEbook). + +The sample application includes the following backend services: + +- An identity microservice, which uses ASP.NET Core Identity and IdentityServer. +- A catalog microservice, which is a data-driven create, read, update, delete (CRUD) service that consumes an SQL Server database using EntityFramework Core. +- An ordering microservice, which is a domain-driven service that uses domain-driven design patterns. +- A basket microservice, which is a data-driven CRUD service that uses Redis Cache. + +These backend services are implemented as microservices using ASP.NET Core MVC, and are deployed as unique containers within a single Docker host. Collectively, these backend services are referred to as the eShopOnContainers reference application. Client apps communicate with the backend services through a Representational State Transfer (REST) web interface. For more information about microservices and Docker, see [Containerized microservices](micro-services.md). + +For information about the implementation of the backend services, see [.NET Microservices: Architecture for Containerized .NET Applications](https://aka.ms/microservicesebook). + +## Multi-Platform app + +This guide focuses on building cross-platform enterprise apps using .NET MAUI, and uses the eShopOnContainers multi-platform app as an example. The image below shows the pages from the eShopOnContainers multi-platform app that provide the functionality outlined earlier. + +![The eShopOnContainers MAUI app](./media/mobile-app-screens.jpg) + +The multi-platform app consumes the backend services provided by the eShopOnContainers reference application. However, it can be configured to consume data from mock services for those who wish to avoid deploying the backend services. + +The eShopOnContainers multi-platform app exercises the following .NET MAUI functionality: + +- XAML +- Controls +- Bindings +- Converters +- Styles +- Animations +- Commands +- Behaviors +- Triggers +- Effects +- Custom Controls + +For more information about this functionality, see the [.NET MAUI documentation](/dotnet/maui) on the Microsoft Developer Center, and [Creating multi-platform apps with .NET MAUI](https://aka.ms/mauiebook). + +In addition, unit tests are provided for some of the classes in the eShopOnContainers multi-platform app. + +## Multi-Platform app solution + +The eShopOnContainers multi-platform app solution organizes the source code and other resources into a multiple projects. All of the core mobile components are contained in a singular project named eShopContainers. This is a feature introduced with .NET 6 that allows a project to target multiple outputs which helps eliminate the need for multiple platform projects that we would have used in Xamarin.Forms and earlier .NET versions. An additional project is included for unit testing. + +While this project has all of its components stored in a singular project, it is worth considering separating it into multiple projects based on your needs. For example, if you have multiple implementations of service providers based off of a service with their own dependencies, it may make sense to break those service provider implementations out into their own separate project. Good candidates for project separation include shared models, service implementations, api client components, database or caching layers. Any place where you feel that the business could re-use a component in another project is a potential candidate for separation. These projects can then be packaged via [NuGet](/nuget/) for easy distribution and versioning. + +All of the projects use folders to organize the source code and other resources into categories. The classes from the eShopOnContainers multi-platform app can be re-used in any .NET MAUI app with little or no modification. + +## eShopOnContainers project + +The eShopOnContainers project contains the following folders: + +| Folder | Description | +|---------------|---------------------------------------------------------------------------------------| +| _Animations_ | Contains classes that enable animations to be consumed in XAML. | +| _Behaviors_ | Contains behaviors that are exposed to view classes. | +| _Controls_ | Contains custom controls used by the app. | +| _Converters_ | Contains value converters that apply custom logic to a binding. | +| _Exceptions_ | Contains the custom ServiceAuthenticationException. | +| _Extensions_ | Contains extension methods for the `VisualElement` and `IEnumerable` classes. | +| _Helpers_ | Contains helper classes for the app. | +| _Models_ | Contains the model classes for the app. | +| _Properties_ | Contains AssemblyInfo.cs, a .NET assembly metadata file. | +| _Services_ | Contains interfaces and classes that implement services that are provided to the app. | +| _Triggers_ | Contains the BeginAnimation trigger, which is used to invoke an animation in XAML. | +| _Validations_ | Contains classes involved in validating data input. | +| _ViewModels_ | Contains the application logic that's exposed to pages. | +| _Views_ | Contains the pages for the app. | + +## Summary + +Microsoft's cross-platform multi-platform app development tools and platforms provide a comprehensive solution for B2E, B2B, and B2C mobile client apps, providing the ability to share code across all target platforms (iOS, macOS, Android, and Windows) and helping to lower the total cost of ownership. Apps can share their user interface and app logic code, while retaining the native platform look and feel. + +Developers of enterprise apps face several challenges that can alter the architecture of the app during development. Therefore, it's important to build an app so that it can be modified or extended over time. Designing for such adaptability can be difficult, but typically involves partitioning an app into discrete, loosely coupled components that can be easily integrated together into an app. diff --git a/docs/architecture/maui/media/asynchronous-event-driven-communication.png b/docs/architecture/maui/media/asynchronous-event-driven-communication.png new file mode 100644 index 0000000000000..65e1d675bbe8d Binary files /dev/null and b/docs/architecture/maui/media/asynchronous-event-driven-communication.png differ diff --git a/docs/architecture/maui/media/authentication-dedicated-authentication-microservice.png b/docs/architecture/maui/media/authentication-dedicated-authentication-microservice.png new file mode 100644 index 0000000000000..79030eced357f Binary files /dev/null and b/docs/architecture/maui/media/authentication-dedicated-authentication-microservice.png differ diff --git a/docs/architecture/maui/media/authorization-by-access-token.png b/docs/architecture/maui/media/authorization-by-access-token.png new file mode 100644 index 0000000000000..d699cdfd49eee Binary files /dev/null and b/docs/architecture/maui/media/authorization-by-access-token.png differ diff --git a/docs/architecture/maui/media/comparison-virtualmachines-containers.png b/docs/architecture/maui/media/comparison-virtualmachines-containers.png new file mode 100644 index 0000000000000..935793ac13835 Binary files /dev/null and b/docs/architecture/maui/media/comparison-virtualmachines-containers.png differ diff --git a/docs/architecture/maui/media/cover-thumbnail.png b/docs/architecture/maui/media/cover-thumbnail.png new file mode 100644 index 0000000000000..bbf4c6f93e2e9 Binary files /dev/null and b/docs/architecture/maui/media/cover-thumbnail.png differ diff --git a/docs/architecture/maui/media/deleting-data-from-the-basket-microservice.png b/docs/architecture/maui/media/deleting-data-from-the-basket-microservice.png new file mode 100644 index 0000000000000..88ea4960cd4ac Binary files /dev/null and b/docs/architecture/maui/media/deleting-data-from-the-basket-microservice.png differ diff --git a/docs/architecture/maui/media/direct-client-to-microservice-communication.png b/docs/architecture/maui/media/direct-client-to-microservice-communication.png new file mode 100644 index 0000000000000..2fb479a2eea11 Binary files /dev/null and b/docs/architecture/maui/media/direct-client-to-microservice-communication.png differ diff --git a/docs/architecture/maui/media/displaying-validation-errors.png b/docs/architecture/maui/media/displaying-validation-errors.png new file mode 100644 index 0000000000000..5b4d61c9ff80d Binary files /dev/null and b/docs/architecture/maui/media/displaying-validation-errors.png differ diff --git a/docs/architecture/maui/media/endpoint_settings.png b/docs/architecture/maui/media/endpoint_settings.png new file mode 100644 index 0000000000000..529e711f8f895 Binary files /dev/null and b/docs/architecture/maui/media/endpoint_settings.png differ diff --git a/docs/architecture/maui/media/enterprise-app-patterns-ebook.png b/docs/architecture/maui/media/enterprise-app-patterns-ebook.png new file mode 100644 index 0000000000000..cb209794c88a1 Binary files /dev/null and b/docs/architecture/maui/media/enterprise-app-patterns-ebook.png differ diff --git a/docs/architecture/maui/media/high-level-architecture-diagram.png b/docs/architecture/maui/media/high-level-architecture-diagram.png new file mode 100644 index 0000000000000..780369ae49060 Binary files /dev/null and b/docs/architecture/maui/media/high-level-architecture-diagram.png differ diff --git a/docs/architecture/maui/media/high-level-overview-sign-in-process.png b/docs/architecture/maui/media/high-level-overview-sign-in-process.png new file mode 100644 index 0000000000000..cb48154ad87d6 Binary files /dev/null and b/docs/architecture/maui/media/high-level-overview-sign-in-process.png differ diff --git a/docs/architecture/maui/media/high-level-overview-sign-out-process.png b/docs/architecture/maui/media/high-level-overview-sign-out-process.png new file mode 100644 index 0000000000000..b1564e08d9cb8 Binary files /dev/null and b/docs/architecture/maui/media/high-level-overview-sign-out-process.png differ diff --git a/docs/architecture/maui/media/login-page-displayed-by-the-webview.png b/docs/architecture/maui/media/login-page-displayed-by-the-webview.png new file mode 100644 index 0000000000000..ee05b1b233cd8 Binary files /dev/null and b/docs/architecture/maui/media/login-page-displayed-by-the-webview.png differ diff --git a/docs/architecture/maui/media/messaging-center.png b/docs/architecture/maui/media/messaging-center.png new file mode 100644 index 0000000000000..1ec84664ed346 Binary files /dev/null and b/docs/architecture/maui/media/messaging-center.png differ diff --git a/docs/architecture/maui/media/microservices-application-scaling.png b/docs/architecture/maui/media/microservices-application-scaling.png new file mode 100644 index 0000000000000..2fe93c29eb6fd Binary files /dev/null and b/docs/architecture/maui/media/microservices-application-scaling.png differ diff --git a/docs/architecture/maui/media/microservices-reference-backend.png b/docs/architecture/maui/media/microservices-reference-backend.png new file mode 100644 index 0000000000000..7241b1253547e Binary files /dev/null and b/docs/architecture/maui/media/microservices-reference-backend.png differ diff --git a/docs/architecture/maui/media/mobile-app-screens.jpg b/docs/architecture/maui/media/mobile-app-screens.jpg new file mode 100644 index 0000000000000..bda86ec6cf49d Binary files /dev/null and b/docs/architecture/maui/media/mobile-app-screens.jpg differ diff --git a/docs/architecture/maui/media/monolith-application-scaling.png b/docs/architecture/maui/media/monolith-application-scaling.png new file mode 100644 index 0000000000000..19fc6fe4c0ea8 Binary files /dev/null and b/docs/architecture/maui/media/monolith-application-scaling.png differ diff --git a/docs/architecture/maui/media/mvvm-pattern.png b/docs/architecture/maui/media/mvvm-pattern.png new file mode 100644 index 0000000000000..c119b92f97328 Binary files /dev/null and b/docs/architecture/maui/media/mvvm-pattern.png differ diff --git a/docs/architecture/maui/media/one-to-many-communication.png b/docs/architecture/maui/media/one-to-many-communication.png new file mode 100644 index 0000000000000..f911a63437562 Binary files /dev/null and b/docs/architecture/maui/media/one-to-many-communication.png differ diff --git a/docs/architecture/maui/media/publish-subscribe-with-event-bus.png b/docs/architecture/maui/media/publish-subscribe-with-event-bus.png new file mode 100644 index 0000000000000..cecd414c77f6a Binary files /dev/null and b/docs/architecture/maui/media/publish-subscribe-with-event-bus.png differ diff --git a/docs/architecture/maui/media/retrieving-data-for-catalog.png b/docs/architecture/maui/media/retrieving-data-for-catalog.png new file mode 100644 index 0000000000000..7f90eb21ea03d Binary files /dev/null and b/docs/architecture/maui/media/retrieving-data-for-catalog.png differ diff --git a/docs/architecture/maui/media/sending-data-to-the-basket-microservice.png b/docs/architecture/maui/media/sending-data-to-the-basket-microservice.png new file mode 100644 index 0000000000000..cf50b61a19eb2 Binary files /dev/null and b/docs/architecture/maui/media/sending-data-to-the-basket-microservice.png differ diff --git a/docs/architecture/maui/media/validation-workflow.png b/docs/architecture/maui/media/validation-workflow.png new file mode 100644 index 0000000000000..43e92d6e4c903 Binary files /dev/null and b/docs/architecture/maui/media/validation-workflow.png differ diff --git a/docs/architecture/maui/micro-services.md b/docs/architecture/maui/micro-services.md new file mode 100644 index 0000000000000..45bdd14b832eb --- /dev/null +++ b/docs/architecture/maui/micro-services.md @@ -0,0 +1,129 @@ +--- +title: Containerized Microservices +description: Strategies for developing scalable back-end architecture +author: michaelstonis +no-loc: [MAUI] +ms.date: 06/29/2022 +--- + +# Containerized Microservices + +[!INCLUDE [download-alert](includes/download-alert.md)] + +Developing client-server applications has resulted in a focus on building tiered applications that use specific technologies in each tier. Such applications are often referred to as _monolithic_ and are packaged onto hardware pre-scaled for peak loads. The main drawbacks of this development approach are the tight coupling between components within each tier, that individual components can't be easily scaled, and the cost of testing. A simple update can have unforeseen effects on the rest of the tier, so a change to an application component requires its entire tier to be retested and redeployed. + +Particularly concerning, in the age of the cloud, is that individual components can't be easily scaled. A monolithic application contains domain-specific functionality and is typically divided by functional layers such as front-end, business logic, and data storage. The image below illustrates that a monolithic application is scaled by cloning the entire application onto multiple machines. + +![Monolithic application scaling approach.](./media/monolith-application-scaling.png) + +## Microservices + +Microservices offer a different approach to application development and deployment, an approach that's suited to the agility, scale, and reliability requirements of modern cloud applications. A microservices application is split into independent components that work together to deliver the application's overall functionality. The term microservice emphasizes that applications should be composed of services small enough to reflect particular concerns, so each microservice implements a single function. In addition, each microservice has well-defined contracts with which other microservices communicate and share data. Typical examples of microservices include shopping carts, inventory processing, purchase subsystems, and payment processing. + +Microservices can scale independently compared to giant monolithic applications that scale together. This means that a specific functional area that requires more processing power or network bandwidth to support demand can be scaled rather than unnecessarily scaling out other application areas. The image below illustrates this approach, where microservices are deployed and scaled independently, creating instances of services across machines. + +![Microservices application scaling approach.](./media/microservices-application-scaling.png) + +Microservice scale-out can be nearly instantaneous, allowing an application to adapt to changing loads. For example, a single microservice in the web-facing functionality of an application might be the only microservice that needs to scale out to handle additional incoming traffic. + +The classic model for application scalability is to have a load-balanced, stateless tier with a shared external datastore to store persistent data. Stateful microservices manage their own persistent data, usually storing it locally on the servers on which they are placed, to avoid the overhead of network access and complexity of cross-service operations. This enables the fastest possible processing of data and can eliminate the need for caching systems. In addition, scalable stateful microservices usually partition data among their instances, in order to manage data size and transfer throughput beyond which a single server can support. + +Microservices also support independent updates. This loose coupling between microservices provides a rapid and reliable application evolution. Their independent, distributed nature helps rolling updates, where only a subset of instances of a single microservice will update at any given time. Therefore, if a problem is detected, a buggy update can be rolled back, before all instances update with the faulty code or configuration. Similarly, microservices typically use schema versioning, so that clients see a consistent version when updates are being applied, regardless of which microservice instance is being communicated with. + +Therefore, microservice applications have many benefits over monolithic applications: + +- Each microservice is relatively small, easy to manage and evolve. +- Each microservice can be developed and deployed independently of other services. +- Each microservice can be scaled-out independently. For example, a catalog service or shopping basket service might need to be scaled-out more than an ordering service. Therefore, the resulting infrastructure will more efficiently consume resources when scaling out. +- Each microservice isolates any issues. For example, if there is an issue in a service it only impacts that service. The other services can continue to handle requests. +- Each microservice can use the latest technologies. Because microservices are autonomous and run side-by-side, the latest technologies and frameworks can be used, rather than being forced to use an older framework that might be used by a monolithic application. + +However, a microservice-based solution also has potential drawbacks: + +- Choosing how to partition an application into microservices can be challenging, as each microservice has to be completely autonomous, end-to-end, including responsibility for its data sources. +- Developers must implement inter-service communication, which adds complexity and latency to the application. +- Atomic transactions between multiple microservices usually aren't possible. Therefore, business requirements must embrace eventual consistency between microservices. +- In production, there is an operational complexity in deploying and managing a system compromised of many independent services. +- Direct client-to-microservice communication can make it difficult to refactor the contracts of microservices. For example, over time how the system is partitioned into services might need to change. A single service might split into two or more services, and two services might merge. When clients communicate directly with microservices, this refactoring work can break compatibility with client apps. + +## Containerization + +Containerization is an approach to software development in which an application and its versioned set of dependencies, plus its environment configuration abstracted as deployment manifest files, are packaged together as a container image, tested as a unit, and deployed to a host operating system. + +A container is an isolated, resource controlled, and portable operating environment, where an application can run without touching the resources of other containers, or the host. Therefore, a container looks and acts like a newly installed physical computer or a virtual machine. + +There are many similarities between containers and virtual machines, as illustrated below. + +![Comparison of virtual machines and containers.](./media/comparison-virtualmachines-containers.png) + +A container runs an operating system, has a file system, and can be accessed over a network as if it were a physical or virtual machine. However, the technology and concepts used by containers are very different from virtual machines. Virtual machines include the applications, the required dependencies, and a full guest operating system. Containers include the application and its dependencies, but share the operating system with other containers, running as isolated processes on the host operating system (aside from Hyper-V containers which run inside of a special virtual machine per container). Therefore, containers share resources and typically require fewer resources than virtual machines. + +The advantage of a container-oriented development and deployment approach is that it eliminates most of the issues that arise from inconsistent environment setups and the problems that come with them. In addition, containers permit fast application scale-up functionality by instancing new containers as required. + +The key concepts when creating and working with containers are: + +| Concept | Description | +|---------|---------| +| Container Host | The physical or virtual machine configured to host containers. The container host will run one or more containers. | +| Container Image | An image consists of a union of layered filesystems stacked on top of each other, and is the basis of a container. An image does not have state and it never changes as it's deployed to different environments. | +| Container | A container is a runtime instance of an image. | +| Container OS Image | Containers are deployed from images. The container operating system image is the first layer in potentially many image layers that make up a container. A container operating system is immutable, and can't be modified. | +| Container Repository | Each time a container image is created, the image and its dependencies are stored in a local repository. These images can be reused many times on the container host. The container images can also be stored in a public or private registry, such as [Docker Hub](https://hub.docker.com/), so that they can be used across different container hosts. | + +Enterprises are increasingly adopting containers when implementing microservice-based applications, and Docker has become the standard container implementation that has been adopted by most software platforms and cloud vendors. + +The eShopOnContainers reference application uses Docker to host four containerized back-end microservices, as illustrated in the diagram below. + +![eShopOnContainers reference application back-end microservices.](./media/microservices-reference-backend.png) + +The architecture of the back-end services in the reference application is decomposed into multiple autonomous sub-systems in the form of collaborating microservices and containers. Each microservice provides a single area of functionality: an identity service, a catalog service, an ordering service, and a basket service. + +Each microservice has its own database, allowing it to be fully decoupled from the other microservices. Where necessary, consistency between databases from different microservices is achieved using application-level events. For more information, see [Communication between microservices](#communication-between-microservices). + +For more information about the reference application, see [.NET Microservices: Architecture for Containerized .NET Applications](https://aka.ms/microservicesebook). + +## Communication between client and microservices + +The eShopOnContainers multi-platform app communicates with the containerized back-end microservices using _direct client-to-microservice_ communication, as shown below. + +![Direct client-to-microservice communication.](./media/direct-client-to-microservice-communication.png) + +With direct client-to-microservice communication, the multi-platform app makes requests to each microservice directly through its public endpoint, with a different TCP port per microservice. In production, the endpoint would typically map to the microservice's load balancer, which distributes requests across the available instances. + +> [!TIP] +> Consider using API gateway communication. + +Direct client-to-microservice communication can have drawbacks when building a large and complex microservice-based application, but it's more than adequate for a small application. Consider using API gateway communication when designing a large microservice-based application with tens of microservices. For more information, see [.NET Microservices: Architecture for Containerized .NET Applications](https://aka.ms/microservicesebook). + +## Communication between microservices + +A microservices-based application is a distributed system, potentially running on multiple machines. Each service instance is typically a process. Therefore, services must interact using an inter-process communication protocol, such as HTTP, TCP, Advanced Message Queuing Protocol (AMQP), or binary protocols, depending on the nature of each service. + +The two common approaches for microservice-to-microservice communication are HTTP-based REST communication when querying for data, and lightweight asynchronous messaging when communicating updates across multiple microservices. + +Asynchronous messaging-based event-driven communication is critical when propagating changes across multiple microservices. With this approach, a microservice publishes an event when something notable happens, for example, when it updates a business entity. Other microservices subscribe to these events. Then, when a microservice receives an event, it updates its own business entities, which might, in turn, lead to more events being published. This publish-subscribe functionality is usually achieved with an event bus. + +An event bus allows publish-subscribe communication between microservices without requiring the components to be explicitly aware of each other, as shown below. + +![Publish-subscribe with an event bus.](./media/publish-subscribe-with-event-bus.png) + +From an application perspective, the event bus is simply a publish-subscribe channel exposed via an interface. However, the way the event bus is implemented can vary. For example, an event bus implementation could use RabbitMQ, Azure Service Bus, or other service buses such as NServiceBus and MassTransit. The diagram below shows how an event bus is used in the eShopOnContainers reference application. + +![Asynchronous event-driven communication in the reference application.](./media/asynchronous-event-driven-communication.png) + +The eShopOnContainers event bus, implemented using RabbitMQ, provides one-to-many asynchronous publish-subscribe functionality. This means that after publishing an event, there can be multiple subscribers listening for the same event. The diagram below illustrates this relationship. + +![One-to-many communication](./media/one-to-many-communication.png) + +This one-to-many communication approach uses events to implement business transactions that span multiple services, ensuring eventual consistency between the services. An eventual-consistent transaction consists of a series of distributed steps. Therefore, when the user-profile microservice receives the UpdateUser command, it updates the user's details in its database and publishes the UserUpdated event to the event bus. Both the basket microservice and the ordering microservice have subscribed to receive this event, and in response, update their buyer information in their respective databases. + +> [!NOTE] +> The eShopOnContainers event bus, implemented using RabbitMQ, is intended to be used only as a proof of concept. For production systems, alternative event bus implementations should be considered. + +For more information about the event bus implementation, see [.NET Microservices: Architecture for Containerized .NET Applications](https://aka.ms/microservicesebook). + +## Summary + +Microservices offer an approach to application development and deployment that's suited to the agility, scale, and reliability requirements of modern cloud applications. One of the main advantages of microservices is that they can be scaled-out independently, which means that a specific functional area can be scaled that requires more processing power or network bandwidth to support demand without unnecessarily scaling areas of the application that are not experiencing increased demand. + +A container is an isolated, resource-controlled, and portable operating environment where an application can run without touching the resources of other containers or the host. Enterprises are increasingly adopting containers when implementing microservice-based applications, and Docker has become the standard container implementation that most software platforms and cloud vendors have adopted. diff --git a/docs/architecture/maui/mvvm.md b/docs/architecture/maui/mvvm.md new file mode 100644 index 0000000000000..c146197be880d --- /dev/null +++ b/docs/architecture/maui/mvvm.md @@ -0,0 +1,366 @@ +--- +title: Model-View-ViewModel +description: Overview of the Model-View-ViewModel pattern used by .NET MAUI +author: michaelstonis +no-loc: [MAUI] +ms.date: 07/01/2022 +--- + +# Model-View-ViewModel (MVVM) + +[!INCLUDE [download-alert](includes/download-alert.md)] + +The .NET MAUI developer experience typically involves creating a user interface in XAML, and then adding code-behind that operates on the user interface. Complex maintenance issues can arise as apps are modified and grow in size and scope. These issues include the tight coupling between the UI controls and the business logic, which increases the cost of making UI modifications, and the difficulty of unit testing such code. + +The MVVM pattern helps cleanly separate an application's business and presentation logic from its user interface (UI). Maintaining a clean separation between application logic and the UI helps address numerous development issues and makes an application easier to test, maintain, and evolve. It can also significantly improve code re-use opportunities and allows developers and UI designers to collaborate more easily when developing their respective parts of an app. + +## The MVVM pattern + +There are three core components in the MVVM pattern: the model, the view, and the view model. Each serves a distinct purpose. The diagram below shows the relationships between the three components. + +![The MVVM pattern](./media/mvvm-pattern.png) + +In addition to understanding the responsibilities of each component, it's also important to understand how they interact. At a high level, the view "knows about" the view model, and the view model "knows about" the model, but the model is unaware of the view model, and the view model is unaware of the view. Therefore, the view model isolates the view from the model, and allows the model to evolve independently of the view. + +The benefits of using the MVVM pattern are as follows: + +- If an existing model implementation encapsulates existing business logic, it can be difficult or risky to change it. In this scenario, the view model acts as an adapter for the model classes and prevents you from making major changes to the model code. +- Developers can create unit tests for the view model and the model, without using the view. The unit tests for the view model can exercise exactly the same functionality as used by the view. +- The app UI can be redesigned without touching the view model and model code, provided that the view is implemented entirely in XAML or C#. Therefore, a new version of the view should work with the existing view model. +- Designers and developers can work independently and concurrently on their components during development. Designers can focus on the view, while developers can work on the view model and model components. + +The key to using MVVM effectively lies in understanding how to factor app code into the correct classes and how the classes interact. The following sections discuss the responsibilities of each of the classes in the MVVM pattern. + +### View + +The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. Ideally, each view is defined in XAML, with a limited code-behind that does not contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior that is difficult to express in XAML, such as animations. + +In a .NET MAUI application, a view is typically a `ContentPage`-derived or `ContentView`-derived class. However, views can also be represented by a data template, which specifies the UI elements to be used to visually represent an object when it's displayed. A data template as a view does not have any code-behind, and is designed to bind to a specific view model type. + +> [!TIP] +> Avoid enabling and disabling UI elements in the code-behind. + +Ensure that the view models are responsible for defining logical state changes that affect some aspects of the view's display, such as whether a command is available, or an indication that an operation is pending. Therefore, enable and disable UI elements by binding to view model properties, rather than enabling and disabling them in code-behind. + +There are several options for executing code on the view model in response to interactions on the view, such as a button click or item selection. If a control supports commands, the control's Command property can be data-bound to an ICommand property on the view model. When the control's command is invoked, the code in the view model will be executed. In addition to commands, behaviors can be attached to an object in the view and can listen for either a command to be invoked or the event to be raised. In response, the behavior can then invoke an ICommand on the view model or a method on the view model. + +### ViewModel + +The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events. The properties and commands that the view model provides define the functionality to be offered by the UI, but the view determines how that functionality is to be displayed. + +> [!TIP] +> Keep the UI responsive with asynchronous operations. + +Multi-platform apps should keep the UI thread unblocked to improve the user's perception of performance. Therefore, in the view model, use asynchronous methods for I/O operations and raise events to asynchronously notify views of property changes. + +The view model is also responsible for coordinating the view's interactions with any model classes that are required. There's typically a one-to-many relationship between the view model and the model classes. The view model might choose to expose model classes directly to the view so that controls in the view can data bind directly to them. In this case, the model classes will need to be designed to support data binding and change notification events. + +Each view model provides data from a model in a form that the view can easily consume. To accomplish this, the view model sometimes performs data conversion. Placing this data conversion in the view model is a good idea because it provides properties that the view can bind to. For example, the view model might combine the values of two properties to make it easier to display by the view. + +> [!TIP] +> Centralize data conversions in a conversion layer. + +It's also possible to use converters as a separate data conversion layer that sits between the view model and the view. This can be necessary, for example, when data requires special formatting that the view model doesn't provide. + +In order for the view model to participate in two-way data binding with the view, its properties must raise the `PropertyChanged` event. View models satisfy this requirement by implementing the `INotifyPropertyChanged` interface, and raising the `PropertyChanged` event when a property is changed. + +For collections, the view-friendly `ObservableCollection` is provided. This collection implements collection changed notification, relieving the developer from having to implement the `INotifyCollectionChanged` interface on collections. + +### Model + +Model classes are non-visual classes that encapsulate the app's data. Therefore, the model can be thought of as representing the app's domain model, which usually includes a data model along with business and validation logic. Examples of model objects include data transfer objects (DTOs), Plain Old CLR Objects (POCOs), and generated entity and proxy objects. + +Model classes are typically used in conjunction with services or repositories that encapsulate data access and caching. + +## Connecting view models to views + +View models can be connected to views by using the data-binding capabilities of .NET MAUI. There are many approaches that can be used to construct views and view models and associate them at runtime. These approaches fall into two categories, known as view first composition, and view model first composition. Choosing between view first composition and view model first composition is an issue of preference and complexity. However, all approaches share the same aim, which is for the view to have a view model assigned to its BindingContext property. + +With view first composition the app is conceptually composed of views that connect to the view models they depend on. The primary benefit of this approach is that it makes it easy to construct loosely coupled, unit testable apps because the view models have no dependence on the views themselves. It's also easy to understand the structure of the app by following its visual structure, rather than having to track code execution to understand how classes are created and associated. In addition, view first construction aligns with the Microsoft Maui's navigation system that's responsible for constructing pages when navigation occurs, which makes a view model first composition complex and misaligned with the platform. + +With view model first composition, the app is conceptually composed of view models, with a service responsible for locating the view for a view model. View model first composition feels more natural to some developers, since the view creation can be abstracted away, allowing them to focus on the logical non-UI structure of the app. In addition, it allows view models to be created by other view models. However, this approach is often complex, and it can become difficult to understand how the various parts of the app are created and associated. + +> [!TIP] +> Keep view models and views independent. + +The binding of views to a property in a data source should be the view's principal dependency on its corresponding view model. Specifically, don't reference view types, such as Button and ListView, from view models. By following the principles outlined here, view models can be tested in isolation, therefore reducing the likelihood of software defects by limiting scope. + +The following sections discuss the main approaches to connecting view models to views. + +## Creating a view model declaratively + +The simplest approach is for the view to declaratively instantiate its corresponding view model in XAML. When the view is constructed, the corresponding view model object will also be constructed. This approach is demonstrated in the following code example: + +```xaml + +     +         +     +     + +``` + +When the `ContentPage` is created, an instance of the `LoginViewModel` is automatically constructed and set as the view's `BindingContext`. + +This declarative construction and assignment of the view model by the view has the advantage that it's simple, but has the disadvantage that it requires a default (parameter-less) constructor in the view model. + +## Creating a view model programmatically + +A view can have code in the code-behind file, resulting in the view-model being assigned to its `BindingContext` property. This is often accomplished in the view's constructor, as shown in the following code example: + +```csharp +public LoginView() +{ +    InitializeComponent(); +    BindingContext = new LoginViewModel(navigationService); +} +``` + +The programmatic construction and assignment of the view model within the view's code-behind has the advantage that it's simple. However, the main disadvantage of this approach is that the view needs to provide the view model with any required dependencies. Using a dependency injection container can help to maintain loose coupling between the view and view model. For more information, see [Dependency injection](dependency-injection.md). + +## Updating views in response to changes in the underlying view model or model + +All view model and model classes that are accessible to a view should implement the [ interface. Implementing this interface in a view model or model class allows the class to provide change notifications to any data-bound controls in the view when the underlying property value changes. + +App's should be architected for the correct use of property change notification, by meeting the following requirements: + +- Always raising a `PropertyChanged` event if a public property's value changes. Do not assume that raising the `PropertyChanged` event can be ignored because of knowledge of how XAML binding occurs. +- Always raising a `PropertyChanged` event for any calculated properties whose values are used by other properties in the view model or model. +- Always raising the `PropertyChanged` event at the end of the method that makes a property change, or when the object is known to be in a safe state. Raising the event interrupts the operation by invoking the event's handlers synchronously. If this happens in the middle of an operation, it might expose the object to callback functions when it is in an unsafe, partially updated state. In addition, it's possible for cascading changes to be triggered by `PropertyChanged` events. Cascading changes generally require updates to be complete before the cascading change is safe to execute. +- Never raising a `PropertyChanged` event if the property does not change. This means that you must compare the old and new values before raising the `PropertyChanged` event. +- Never raising the `PropertyChanged` event during a view model's constructor if you are initializing a property. Data-bound controls in the view will not have subscribed to receive change notifications at this point. +- Never raising more than one `PropertyChanged` event with the same property name argument within a single synchronous invocation of a public method of a class. For example, given a `NumberOfItems` property whose backing store is the `_numberOfItems` field, if a method increments `_numberOfItems` fifty times during the execution of a loop, it should only raise property change notification on the `NumberOfItems` property once, after all the work is complete. For asynchronous methods, raise the `PropertyChanged` event for a given property name in each synchronous segment of an asynchronous continuation chain. + +A simple way to provide this functionality would be to create an extension of the `BindableObject` class. In this example, the `ExtendedBindableObject` class provides change notifications, which is shown in the following code example: + +```csharp +public abstract class ExtendedBindableObject : BindableObject +{ +    public void RaisePropertyChanged(Expression> property) +    { +        var name = GetMemberInfo(property).Name; +        OnPropertyChanged(name); +    } + +    private MemberInfo GetMemberInfo(Expression expression) +    { +        // Omitted for brevity ... +    } +} +``` + +.NET MAUI's `BindableObject` class implements the `INotifyPropertyChanged` interface, and provides an `OnPropertyChanged` method. The `ExtendedBindableObject` class provides the `RaisePropertyChanged` method to invoke property change notification, and in doing so uses the functionality provided by the `BindableObject` class. + +View model classes can then derive from the `ExtendedBindableObject` class. Therefore, each view model class uses the `RaisePropertyChanged` method in the `ExtendedBindableObject` class to provide property change notification. The following code example shows how the eShopOnContainers multi-platform app invokes property change notification by using a lambda expression: + +```csharp +public bool IsLogin +{ +    get => _isLogin; +    set +    { +        _isLogin = value; +        RaisePropertyChanged(() => IsLogin); +    } +} +``` + +Using a lambda expression in this way involves a small performance cost because the lambda expression has to be evaluated for each call. Although the performance cost is small and would not typically impact an app, the costs can accrue when there are many change notifications. However, the benefit of this approach is that it provides compile-time type safety and refactoring support when renaming properties. + +## MVVM Frameworks + +The MVVM pattern is well established in .NET, and the community has created many frameworks which help ease this development. Each framework provides a different set of features, but it is standard for them to provide a common view model with an implementation of the `INotifyPropertyChanged` interface. Additional features of MVVM frameworks include custom commands, navigation helpers, dependency injection/service locator components, and UI platform integration. While it is not necessary to use these frameworks, they can speed up and standardize your development. The eShopOnContainers multi-platform app uses [the .NET Community MVVM Toolkit](/windows/communitytoolkit/mvvm/introduction). When choosing a framework, you should consider your application's needs and your team's strengths. The list below includes some of the more common MVVM frameworks for .NET MAUI. + +- [.NET Community MVVM Toolkit](/windows/communitytoolkit/mvvm/introduction/) +- [ReactiveUI](https://www.reactiveui.net/) +- [Prism Library](https://prismlibrary.com/) + +## UI interaction using commands and behaviors + +In multi-platform apps, actions are typically invoked in response to a user action, such as a button click, that can be implemented by creating an event handler in the code-behind file. However, in the MVVM pattern, the responsibility for implementing the action lies with the view model, and placing code in the code-behind should be avoided. + +Commands provide a convenient way to represent actions that can be bound to controls in the UI. They encapsulate the code that implements the action and help to keep it decoupled from its visual representation in the view. This way, your view models become more portable to new platforms, as they do not have a direct dependency on events provided by the platform's UI framework. .NET MAUI includes controls that can be declaratively connected to a command, and these controls will invoke the command when the user interacts with the control. + +Behaviors also allow controls to be declaratively connected to a command. However, behaviors can be used to invoke an action that's associated with a range of events raised by a control. Therefore, behaviors address many of the same scenarios as command-enabled controls, while providing a greater degree of flexibility and control. In addition, behaviors can also be used to associate command objects or methods with controls that were not specifically designed to interact with commands. + +## Implementing commands + +View models typically expose public properties, for binding from the view, which implement the `ICommand` interface. Many .NET MAUI controls and gestures provide a `Command` property, which can be data bound to an `ICommand` object provided by the view model. The button control is one of the most commonly used controls, providing a command property that executes when the button is clicked. + +> [!NOTE] +> While it is possible to expose the actual implementation of the `ICommand` interface that your view model uses (e.g. `Command` or `RelayCommand`), it is recommended to expose your commands publicly as `ICommand`. This way, if you ever need to change the implementation at a later date, it can easily be swapped out. + +The `ICommand` interface defines an `Execute` method, which encapsulates the operation itself, a `CanExecute` method, which indicates whether the command can be invoked, and a `CanExecuteChanged` event that occurs when changes occur that affect whether the command should execute. In most cases, we will only supply the `Execute` method for our commands. For a more detailed overview of `ICommand`, refer to the [Commanding](/dotnet/maui/fundamentals/data-binding/commanding) documentation for .NET MAUI. + +Provided with .NET MAUI are the `Command` and `Command` classes that implement the `ICommand` interface, where `T` is the type of the arguments to `Execute` and `CanExecute`. `Command` and `Command` are basic implementations that provide the minimal set of functionality needed for the `ICommand` interface. + +> [!NOTE] +> Many MVVM frameworks offer more feature rich implementations of the `ICommand` interface. + +The `Command` or `Command` constructor requires an Action callback object that's called when the `ICommand.Execute` method is invoked. The `CanExecute` method is an optional constructor parameter, and is a Func that returns a bool. + +The eShopOnContainers multi-platform app uses the [RelayCommand](/windows/communitytoolkit/mvvm/relaycommand) and [AsyncRelayCommand](/windows/communitytoolkit/mvvm/asyncrelaycommand). The primary benefit for modern applications is that the `AsyncRelayCommand` provides better functionality for asynchronous operations. + +The following code shows how a `Command` instance, which represents a register command, is constructed by specifying a delegate to the Register view model method: + +```csharp +public ICommand RegisterCommand { get; } +``` + +The command is exposed to the view through a property that returns a reference to an `ICommand`. When the `Execute` method is called on the `Command` object, it simply forwards the call to the method in the view model via the delegate that was specified in the `Command` constructor. An asynchronous method can be invoked by a command by using the async and await keywords when specifying the command's `Execute` delegate. This indicates that the callback is a `Task` and should be awaited. For example, the following code shows how an `ICommand` instance, which represents a sign-in command, is constructed by specifying a delegate to the `SignInAsync` view model method: + +```csharp +public ICommand SignInCommand { get; } +... +SignInCommand = new AsyncRelayCommand(async () => await SignInAsync()); +``` + +Parameters can be passed to the `Execute` and `CanExecute` actions by using the `AsyncRelayCommand` class to instantiate the command. For example, the following code shows how an `AsyncRelayCommand` instance is used to indicate that the `NavigateAsync` method will require an argument of type string: + +```csharp +public ICommand NavigateCommand { get; } + +... +NavigateCommand = new AsyncRelayCommand(NavigateAsync); +``` + +In both the `RelayCommand` and `RelayCommand` classes, the delegate to the `CanExecute` method in each constructor is optional. If a delegate isn't specified, the `Command` will return true for `CanExecute`. However, the view model can indicate a change in the command's `CanExecute` status by calling the `ChangeCanExecute` method on the `Command` object. This causes the `CanExecuteChanged` event to be raised. Any UI controls bound to the command will then update their enabled status to reflect the availability of the data-bound command. + +## Invoking commands from a view + +The following code example shows how a `Grid` in the `LoginView` binds to the `RegisterCommand` in the `LoginViewModel` class by using a `TapGestureRecognizer` instance: + +```xml + +