diff --git a/.github/workflows/version-sweep.yml b/.github/workflows/version-sweep.yml index 7aec67dde084c..40a1b55ac964d 100644 --- a/.github/workflows/version-sweep.yml +++ b/.github/workflows/version-sweep.yml @@ -1,84 +1,37 @@ -# This is a basic workflow to help you get started with Actions - name: "target supported version" -# 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 * *" - workflow_dispatch: - inputs: - reason: - description: "The reason for running the workflow" - required: true - default: "Manual run" - support: - description: "The support level to target (STS, LTS, or Preview)." - required: true - default: "STS" + # Triggers the workflow on push or pull request events but only for the default branch + schedule: + - cron: "0 0 1 * *" + workflow_dispatch: + inputs: + reason: + description: "The reason for running the workflow" + required: true + default: "Manual run" + support: + description: "The support level to target (STS, LTS, or Preview)." + required: true + default: "STS" # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "build" - version-sweep: - # The type of runner that the job will run on - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - - # Runs a single command using the runners shell - - name: "Print manual run reason" - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - echo 'Reason: ${{ github.event.inputs.reason }}' - - # Start the .NET version sweeper, scan projects/slns for non-LTS (or currrent) versions - - name: .NET version sweeper - id: dotnet-version-sweeper - uses: dotnet/versionsweeper@main - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - owner: ${{ github.repository_owner }} - name: ${{ github.repository }} - branch: ${{ github.ref }} - - - name: Create pull requests - if: steps.dotnet-version-sweeper.outputs.has-remaining-work == 'true' - run: | - upgradeProjects: ${{ steps.dotnet-version-sweeper.outputs.upgradeProjects }} - - # Install .NET Upgrade Assistant global tool - dotnet tool install --global upgrade-assistant - - # Iterate all upgrade projects - for projectDir in "${upgradeProjects[@]}"; do - echo "Project Directory: $projectDir" - - # Create a new branch - git checkout -b upgrade/$projectDir - - # Perform the upgrade using upgrade-assistant - upgrade-assistant upgrade "$projectDir" --non-interactive -t ${{ inputs.support }} - - # Commit the changes - git add . - git commit -m ".NET Version Sweeper: Upgraded $projectDir" - - # Push the branch to the repository - git push origin upgrade/$projectDir - - # Create a pull request - gh pr create \ - --base main \ - --head upgrade/$projectDir \ - --title "Upgraded $projectDir" \ - --body "Proposed upgrade for $projectDir" - done + # This workflow contains a single job called "build" + version-sweep: + # The type of runner that the job will run on + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + + # Start the .NET version updater action + # A composite of the .NET Version Sweeper and the .NET Upgrade Assistant + - name: .NET version updater + id: dotnet-version-updater + uses: dotnet/docs-tools/actions/dotnet-version-updater@main + with: + support: ${{ github.event.inputs.support }} \ No newline at end of file diff --git a/docs/azure/TOC.yml b/docs/azure/TOC.yml index 5141db1a48edb..c32d50341c3eb 100644 --- a/docs/azure/TOC.yml +++ b/docs/azure/TOC.yml @@ -61,6 +61,8 @@ href: ./sdk/logging.md - name: Pagination href: ./sdk/pagination.md + - name: Unit testing and mocking + href: ./sdk/unit-testing-mocking.md - name: Configure a proxy server href: ./sdk/azure-sdk-configure-proxy.md - name: Packages list diff --git a/docs/azure/includes/dotnet-all.md b/docs/azure/includes/dotnet-all.md index 1864ef53cbcd2..b1b3d1da5ec20 100644 --- a/docs/azure/includes/dotnet-all.md +++ b/docs/azure/includes/dotnet-all.md @@ -462,7 +462,7 @@ | Microsoft.Azure.Functions.Worker.Extensions.SignalRService | NuGet [1.10.0](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.SignalRService/1.10.0) | | | | Microsoft.Azure.Functions.Worker.Extensions.Sql | NuGet [3.0.181-preview](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Sql/3.0.181-preview) | | | | Microsoft.Azure.Functions.Worker.Extensions.Storage | NuGet [5.1.2](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage/5.1.2)
NuGet [5.1.3-preview1](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage/5.1.3-preview1) | | | -| Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs | NuGet [5.1.2](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs/5.1.2)
NuGet [5.1.3-preview1](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs/5.1.3-preview1) | | | +| Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs | NuGet [6.0.0](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs/6.0.0) | | | | Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues | NuGet [5.1.2](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues/5.1.2)
NuGet [5.1.3-preview1](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues/5.1.3-preview1) | | | | Microsoft.Azure.Functions.Worker.Extensions.Storage.Tables | NuGet [1.0.0-preview1](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Storage.Tables/1.0.0-preview1) | | | | Microsoft.Azure.Functions.Worker.Extensions.Tables | NuGet [1.0.0](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Tables/1.0.0)
NuGet [1.2.0-preview1](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.Tables/1.2.0-preview1) | | | diff --git a/docs/azure/includes/dotnet-new.md b/docs/azure/includes/dotnet-new.md index 4aa9d03bf7443..ebeca472facfd 100644 --- a/docs/azure/includes/dotnet-new.md +++ b/docs/azure/includes/dotnet-new.md @@ -60,7 +60,7 @@ | Purview Administration | NuGet [1.0.0-beta.1](https://www.nuget.org/packages/Azure.Analytics.Purview.Administration/1.0.0-beta.1) | [docs](/dotnet/api/overview/azure/Analytics.Purview.Administration-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Analytics.Purview.Administration_1.0.0-beta.1/sdk/purview/Azure.Analytics.Purview.Administration/) | | Purview Catalog | NuGet [1.0.0-beta.4](https://www.nuget.org/packages/Azure.Analytics.Purview.Catalog/1.0.0-beta.4) | [docs](/dotnet/api/overview/azure/Analytics.Purview.Catalog-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.4](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Analytics.Purview.Catalog_1.0.0-beta.4/sdk/purview/Azure.Analytics.Purview.Catalog/) | | Purview Scanning | NuGet [1.0.0-beta.2](https://www.nuget.org/packages/Azure.Analytics.Purview.Scanning/1.0.0-beta.2) | [docs](/dotnet/api/overview/azure/Analytics.Purview.Scanning-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Analytics.Purview.Scanning_1.0.0-beta.2/sdk/purview/Azure.Analytics.Purview.Scanning/) | -| Purview Share | NuGet [1.0.3-beta.20](https://www.nuget.org/packages/Azure.Analytics.Purview.Share/1.0.3-beta.20) | [docs](/dotnet/api/overview/azure/Analytics.Purview.Share-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.3-beta.20](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Analytics.Purview.Share_1.0.3-beta.20/sdk/purview/Azure.Analytics.Purview.Share/) | +| Purview Share | NuGet [1.0.3-beta.20](https://www.nuget.org/packages/Azure.Analytics.Purview.Share/1.0.3-beta.20) | [docs](/dotnet/api/overview/azure/Analytics.Purview.Share-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.3-beta.20](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Analytics.Purview.Share_1.0.3-beta.20/sdk/purview/Azure.Analytics.Purview.Share) | | Purview Sharing | NuGet [1.0.0-beta.3](https://www.nuget.org/packages/Azure.Analytics.Purview.Sharing/1.0.0-beta.3) | [docs](/dotnet/api/overview/azure/Analytics.Purview.Sharing-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.3](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Analytics.Purview.Sharing_1.0.0-beta.3/sdk/purview/Azure.Analytics.Purview.Sharing/) | | Purview Workflow | NuGet [1.0.0-beta.1](https://www.nuget.org/packages/Azure.Analytics.Purview.Workflows/1.0.0-beta.1) | [docs](/dotnet/api/overview/azure/Analytics.Purview.Workflows-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Analytics.Purview.Workflows_1.0.0-beta.1/sdk/purview/Azure.Analytics.Purview.Workflows/) | | Question Answering | NuGet [1.1.0](https://www.nuget.org/packages/Azure.AI.Language.QuestionAnswering/1.1.0) | [docs](/dotnet/api/overview/azure/AI.Language.QuestionAnswering-readme) | GitHub [1.1.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.AI.Language.QuestionAnswering_1.1.0/sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/) | @@ -181,7 +181,7 @@ | Resource Management - Hybrid Compute | NuGet [1.0.0-beta.4](https://www.nuget.org/packages/Azure.ResourceManager.HybridCompute/1.0.0-beta.4) | [docs](/dotnet/api/overview/azure/ResourceManager.HybridCompute-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.4](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.HybridCompute_1.0.0-beta.4/sdk/hybridcompute/Azure.ResourceManager.HybridCompute/) | | Resource Management - Hybrid Connectivity | NuGet [1.0.0-beta.3](https://www.nuget.org/packages/Azure.ResourceManager.HybridConnectivity/1.0.0-beta.3) | [docs](/dotnet/api/overview/azure/ResourceManager.HybridConnectivity-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.3](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.HybridConnectivity_1.0.0-beta.3/sdk/hybridconnectivity/Azure.ResourceManager.HybridConnectivity/) | | Resource Management - Hybrid Container Service | NuGet [1.0.0-beta.2](https://www.nuget.org/packages/Azure.ResourceManager.HybridContainerService/1.0.0-beta.2) | [docs](/dotnet/api/overview/azure/ResourceManager.HybridContainerService-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.HybridContainerService_1.0.0-beta.2/sdk/hybridaks/Azure.ResourceManager.HybridContainerService/) | -| Resource Management - Hybrid Data | NuGet [1.0.1](https://www.nuget.org/packages/Azure.ResourceManager.HybridData/1.0.1)
NuGet [1.1.0-beta.1](https://www.nuget.org/packages/Azure.ResourceManager.HybridData/1.1.0-beta.1) | [docs](/dotnet/api/overview/azure/ResourceManager.HybridData-readme) | GitHub [1.0.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.HybridData_1.0.1/sdk/hybriddatamanager/Azure.ResourceManager.HybridData/)
GitHub [1.1.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.HybridData_1.1.0-beta.1/sdk/hybriddatamanager/Azure.ResourceManager.HybridData/) | +| Resource Management - Hybrid Data | NuGet [1.0.1](https://www.nuget.org/packages/Azure.ResourceManager.HybridData/1.0.1)
NuGet [1.1.0-beta.1](https://www.nuget.org/packages/Azure.ResourceManager.HybridData/1.1.0-beta.1) | [docs](/dotnet/api/overview/azure/ResourceManager.HybridData-readme) | GitHub [1.0.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.HybridData_1.1.0-beta.1/sdk/hybriddatamanager/Azure.ResourceManager.HybridData) | | Resource Management - Hybrid Kubernetes | NuGet [1.0.0-beta.3](https://www.nuget.org/packages/Azure.ResourceManager.Kubernetes/1.0.0-beta.3) | [docs](/dotnet/api/overview/azure/ResourceManager.Kubernetes-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.3](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Kubernetes_1.0.0-beta.3/sdk/hybridkubernetes/Azure.ResourceManager.Kubernetes/) | | Resource Management - IoT Central | NuGet [1.0.1](https://www.nuget.org/packages/Azure.ResourceManager.IotCentral/1.0.1)
NuGet [1.1.0-beta.1](https://www.nuget.org/packages/Azure.ResourceManager.IotCentral/1.1.0-beta.1) | [docs](/dotnet/api/overview/azure/ResourceManager.IotCentral-readme) | GitHub [1.0.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.IotCentral_1.0.1/sdk/iotcentral/Azure.ResourceManager.IotCentral/)
GitHub [1.1.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.IotCentral_1.1.0-beta.1/sdk/iotcentral/Azure.ResourceManager.IotCentral/) | | Resource Management - IoT Hub | NuGet [1.0.1](https://www.nuget.org/packages/Azure.ResourceManager.IotHub/1.0.1)
NuGet [1.1.0-beta.1](https://www.nuget.org/packages/Azure.ResourceManager.IotHub/1.1.0-beta.1) | [docs](/dotnet/api/overview/azure/ResourceManager.IotHub-readme) | GitHub [1.0.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.IotHub_1.0.1/sdk/iothub/Azure.ResourceManager.IotHub/)
GitHub [1.1.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.IotHub_1.1.0-beta.1/sdk/iothub/Azure.ResourceManager.IotHub/) | diff --git a/docs/azure/sdk/snippets/unit-testing/AboutToExpireSecretsFinder.cs b/docs/azure/sdk/snippets/unit-testing/AboutToExpireSecretsFinder.cs new file mode 100644 index 0000000000000..072f714272ec7 --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/AboutToExpireSecretsFinder.cs @@ -0,0 +1,29 @@ +using Azure.Security.KeyVault.Secrets; + +public class AboutToExpireSecretFinder +{ + private readonly TimeSpan _threshold; + private readonly SecretClient _client; + + public AboutToExpireSecretFinder(TimeSpan threshold, SecretClient client) + { + _threshold = threshold; + _client = client; + } + + public async Task GetAboutToExpireSecretsAsync() + { + List secretsAboutToExpire = new(); + + await foreach (var secret in _client.GetPropertiesOfSecretsAsync()) + { + if (secret.ExpiresOn.HasValue && + secret.ExpiresOn.Value - DateTimeOffset.Now <= _threshold) + { + secretsAboutToExpire.Add(secret.Name); + } + } + + return secretsAboutToExpire.ToArray(); + } +} \ No newline at end of file diff --git a/docs/azure/sdk/snippets/unit-testing/Moq/AboutToExpireSecretsFinderTests_Moq.cs b/docs/azure/sdk/snippets/unit-testing/Moq/AboutToExpireSecretsFinderTests_Moq.cs new file mode 100644 index 0000000000000..ec9b1e662959e --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/Moq/AboutToExpireSecretsFinderTests_Moq.cs @@ -0,0 +1,71 @@ +using Azure; +using Azure.Security.KeyVault.Secrets; +using Moq; + +namespace UnitTestingSampleApp.Moq; + +public class AboutToExpireSecretFinderTests_Moq +{ + [Fact] + public async Task DoesNotReturnNonExpiringSecrets() + { + // Arrange + // Create a page of enumeration results + Page page = Page.FromValues(new[] + { + new SecretProperties("secret1") { ExpiresOn = null }, + new SecretProperties("secret2") { ExpiresOn = null } + }, null, Mock.Of()); + + // Create a pageable that consists of a single page + AsyncPageable pageable = + AsyncPageable.FromPages(new[] { page }); + + // Setup a client mock object to return the pageable + var clientMock = new Mock(); + clientMock.Setup(c => c.GetPropertiesOfSecretsAsync(It.IsAny())) + .Returns(pageable); + + // Create an instance of a class to test passing in the mock client + var finder = new AboutToExpireSecretFinder(TimeSpan.FromDays(2), clientMock.Object); + + // Act + string[] soonToExpire = await finder.GetAboutToExpireSecretsAsync(); + + // Assert + Assert.Empty(soonToExpire); + } + + [Fact] + public async Task ReturnsSecretsThatExpireSoon() + { + // Arrange + + // Create a page of enumeration results + DateTimeOffset now = DateTimeOffset.Now; + Page page = Page.FromValues(new[] + { + new SecretProperties("secret1") { ExpiresOn = now.AddDays(1) }, + new SecretProperties("secret2") { ExpiresOn = now.AddDays(2) }, + new SecretProperties("secret3") { ExpiresOn = now.AddDays(3) } + }, null, Mock.Of()); + + // Create a pageable that consists of a single page + AsyncPageable pageable = + AsyncPageable.FromPages(new[] { page }); + + // Setup a client mock object to return the pageable + var clientMock = new Mock(); + clientMock.Setup(c => c.GetPropertiesOfSecretsAsync(It.IsAny())) + .Returns(pageable); + + // Create an instance of a class to test passing in the mock client + var finder = new AboutToExpireSecretFinder(TimeSpan.FromDays(2), clientMock.Object); + + // Act + string[] soonToExpire = await finder.GetAboutToExpireSecretsAsync(); + + // Assert + Assert.Equal(new[] { "secret1", "secret2" }, soonToExpire); + } +} diff --git a/docs/azure/sdk/snippets/unit-testing/Moq/TestSnippets_Moq.cs b/docs/azure/sdk/snippets/unit-testing/Moq/TestSnippets_Moq.cs new file mode 100644 index 0000000000000..11fe22aa7a747 --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/Moq/TestSnippets_Moq.cs @@ -0,0 +1,100 @@ +using Azure.Security.KeyVault.Secrets; +using Azure; +using Moq; + +namespace UnitTestingSampleApp.Moq; + +public class TestSnippets_Moq +{ + public void ServiceClientSnippets() + { + // + KeyVaultSecret keyVaultSecret = SecretModelFactory.KeyVaultSecret( + new SecretProperties("secret"), "secretValue"); + + Mock clientMock = new Mock(); + clientMock.Setup(c => c.GetSecret( + It.IsAny(), + It.IsAny(), + It.IsAny()) + ) + .Returns(Response.FromValue(keyVaultSecret, Mock.Of())); + + clientMock.Setup(c => c.GetSecretAsync( + It.IsAny(), + It.IsAny(), + It.IsAny()) + ) + .ReturnsAsync(Response.FromValue(keyVaultSecret, Mock.Of())); + + SecretClient secretClient = clientMock.Object; + // + } + + public void ResponseTypeSnippets() + { + // + Mock responseMock = new Mock(); + responseMock.SetupGet(r => r.Status).Returns(200); + + Response response = responseMock.Object; + // + } + + public void ResponseTypeTSnippets() + { + // + KeyVaultSecret keyVaultSecret = SecretModelFactory.KeyVaultSecret( + new SecretProperties("secret"), "secretValue"); + Response response = Response.FromValue(keyVaultSecret, Mock.Of()); + // + } + + public void PaggingSnippets() + { + // + Page responsePage = Page.FromValues( + new[] { + new SecretProperties("secret1"), + new SecretProperties("secret2") + }, + continuationToken: null, + Mock.Of()); + // + + // + Page page1 = Page.FromValues( + new[] + { + new SecretProperties("secret1"), + new SecretProperties("secret2") + }, + "continuationToken", + Mock.Of()); + + Page page2 = Page.FromValues( + new[] + { + new SecretProperties("secret3"), + new SecretProperties("secret4") + }, + "continuationToken2", + Mock.Of()); + + Page lastPage = Page.FromValues( + new[] + { + new SecretProperties("secret5"), + new SecretProperties("secret6") + }, + continuationToken: null, + Mock.Of()); + + Pageable pageable = Pageable + .FromPages(new[] { page1, page2, lastPage }); + + AsyncPageable asyncPageable = AsyncPageable + .FromPages(new[] { page1, page2, lastPage }); + // + } +} diff --git a/docs/azure/sdk/snippets/unit-testing/NSubstitute/AboutToExpireSecretsFinderTests_NSubstitute.cs b/docs/azure/sdk/snippets/unit-testing/NSubstitute/AboutToExpireSecretsFinderTests_NSubstitute.cs new file mode 100644 index 0000000000000..31bc66a35a3f7 --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/NSubstitute/AboutToExpireSecretsFinderTests_NSubstitute.cs @@ -0,0 +1,71 @@ +using Azure; +using Azure.Security.KeyVault.Secrets; +using NSubstitute; + +namespace UnitTestingSampleApp.NSubstitute; + +public class AboutToExpireSecretFinderTests_NSubstitute +{ + [Fact] + public async Task DoesNotReturnNonExpiringSecrets() + { + // Arrange + // Create a page of enumeration results + Page page = Page.FromValues(new[] + { + new SecretProperties("secret1") { ExpiresOn = null }, + new SecretProperties("secret2") { ExpiresOn = null } + }, null, Substitute.For()); + + // Create a pageable that consists of a single page + AsyncPageable pageable = + AsyncPageable.FromPages(new[] { page }); + + // Setup a client mock object to return the pageable + SecretClient clientMock = Substitute.For(); + clientMock.GetPropertiesOfSecretsAsync(Arg.Any()) + .Returns(pageable); + + // Create an instance of a class to test passing in the mock client + var finder = new AboutToExpireSecretFinder(TimeSpan.FromDays(2), clientMock); + + // Act + var soonToExpire = await finder.GetAboutToExpireSecretsAsync(); + + // Assert + Assert.Empty(soonToExpire); + } + + [Fact] + public async Task ReturnsSecretsThatExpireSoon() + { + // Arrange + + // Create a page of enumeration results + DateTimeOffset now = DateTimeOffset.Now; + Page page = Page.FromValues(new[] + { + new SecretProperties("secret1") { ExpiresOn = now.AddDays(1) }, + new SecretProperties("secret2") { ExpiresOn = now.AddDays(2) }, + new SecretProperties("secret3") { ExpiresOn = now.AddDays(3) } + }, null,Substitute.For()); + + // Create a pageable that consists of a single page + AsyncPageable pageable = + AsyncPageable.FromPages(new[] { page }); + + // Setup a client mock object to return the pageable + SecretClient clientMock = Substitute.For(); + clientMock.GetPropertiesOfSecretsAsync(Arg.Any()) + .Returns(pageable); + + // Create an instance of a class to test passing in the mock client + var finder = new AboutToExpireSecretFinder(TimeSpan.FromDays(2), clientMock); + + // Act + var soonToExpire = await finder.GetAboutToExpireSecretsAsync(); + + // Assert + Assert.Equal(new[] { "secret1", "secret2" }, soonToExpire); + } +} diff --git a/docs/azure/sdk/snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs b/docs/azure/sdk/snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs new file mode 100644 index 0000000000000..5d4d059f6225a --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs @@ -0,0 +1,102 @@ +using Azure.Security.KeyVault.Secrets; +using Azure; +using NSubstitute; + +namespace UnitTestingSampleApp.NSubstitute; + +public class TestSnippets_NSubstitute +{ + public void ServiceClientSnippets() + { + // + KeyVaultSecret keyVaultSecret = SecretModelFactory.KeyVaultSecret( + new SecretProperties("secret"), "secretValue"); + + SecretClient clientMock = Substitute.For(); + clientMock.GetSecret( + Arg.Any(), + Arg.Any(), + Arg.Any() + ) + .Returns(Response.FromValue(keyVaultSecret, Substitute.For())); + + clientMock.GetSecretAsync( + Arg.Any(), + Arg.Any(), + Arg.Any() + ) + .Returns(Response.FromValue(keyVaultSecret, Substitute.For())); + + SecretClient secretClient = clientMock; + // + } + + public void ResponseTypeSnippets() + { + // + Response responseMock = Substitute.For(); + responseMock.Status.Returns(200); + + Response response = responseMock; + // + } + + public void ResponseTypeTSnippets() + { + // + KeyVaultSecret keyVaultSecret = SecretModelFactory.KeyVaultSecret( + new SecretProperties("secret"), "secretValue"); + Response response = Response.FromValue(keyVaultSecret, Substitute.For()); + // + } + + + public void PaggingSnippets() + { + // + Page responsePage = Page.FromValues( + new[] { + new SecretProperties("secret1"), + new SecretProperties("secret2") + }, + continuationToken: null, + Substitute.For()); + // + + // + Page page1 = Page.FromValues( + new[] + { + new SecretProperties("secret1"), + new SecretProperties("secret2") + }, + "continuationToken", + Substitute.For()); + + Page page2 = Page.FromValues( + new[] + { + new SecretProperties("secret3"), + new SecretProperties("secret4") + }, + "continuationToken2", + Substitute.For()); + + Page lastPage = Page.FromValues( + new[] + { + new SecretProperties("secret5"), + new SecretProperties("secret6") + }, + continuationToken: null, + Substitute.For()); + + Pageable pageable = Pageable + .FromPages(new[] { page1, page2, lastPage }); + + AsyncPageable asyncPageable = AsyncPageable + .FromPages(new[] { page1, page2, lastPage }); + // + } + +} diff --git a/docs/azure/sdk/snippets/unit-testing/NonLibrary/AboutToExpireSecretsFinderTests.cs b/docs/azure/sdk/snippets/unit-testing/NonLibrary/AboutToExpireSecretsFinderTests.cs new file mode 100644 index 0000000000000..d1c8570e3fc21 --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/NonLibrary/AboutToExpireSecretsFinderTests.cs @@ -0,0 +1,66 @@ +using Azure; +using Azure.Security.KeyVault.Secrets; + +namespace UnitTestingSampleApp.NonLibrary; + +public class AboutToExpireSecretFinderTests +{ + [Fact] + public async Task DoesNotReturnNonExpiringSecrets() + { + // Arrange + // Create a page of enumeration results + Page page = Page.FromValues(new[] + { + new SecretProperties("secret1") { ExpiresOn = null }, + new SecretProperties("secret2") { ExpiresOn = null } + }, null, new MockResponse()); + + // Create a pageable that consists of a single page + AsyncPageable pageable = + AsyncPageable.FromPages(new[] { page }); + + var clientMock = new MockSecretClient(pageable); + + // Create an instance of a class to test passing in the mock client + var finder = new AboutToExpireSecretFinder(TimeSpan.FromDays(2), clientMock); + + // Act + string[] soonToExpire = await finder.GetAboutToExpireSecretsAsync(); + + // Assert + Assert.Empty(soonToExpire); + } + + [Fact] + public async Task ReturnsSecretsThatExpireSoon() + { + // Arrange + + // Create a page of enumeration results + DateTimeOffset now = DateTimeOffset.Now; + Page page = Page.FromValues(new[] + { + new SecretProperties("secret1") { ExpiresOn = now.AddDays(1) }, + new SecretProperties("secret2") { ExpiresOn = now.AddDays(2) }, + new SecretProperties("secret3") { ExpiresOn = now.AddDays(3) } + }, + null, new MockResponse()); + + // Create a pageable that consists of a single page + AsyncPageable pageable = + AsyncPageable.FromPages(new[] { page }); + + // Create a client mock object + var clientMock = new MockSecretClient(pageable); + + // Create an instance of a class to test passing in the mock client + var finder = new AboutToExpireSecretFinder(TimeSpan.FromDays(2), clientMock); + + // Act + string[] soonToExpire = await finder.GetAboutToExpireSecretsAsync(); + + // Assert + Assert.Equal(new[] { "secret1", "secret2" }, soonToExpire); + } +} diff --git a/docs/azure/sdk/snippets/unit-testing/NonLibrary/MockResponse.cs b/docs/azure/sdk/snippets/unit-testing/NonLibrary/MockResponse.cs new file mode 100644 index 0000000000000..099c8e63e1478 --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/NonLibrary/MockResponse.cs @@ -0,0 +1,38 @@ +using Azure.Core; +using Azure; +using System.Diagnostics.CodeAnalysis; + +namespace UnitTestingSampleApp.NonLibrary; + +public sealed class MockResponse : Response +{ + public override int Status => throw new NotImplementedException(); + + public override string ReasonPhrase => throw new NotImplementedException(); + + public override Stream? ContentStream + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + public override string ClientRequestId + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override void Dispose() => + throw new NotImplementedException(); + protected override bool ContainsHeader(string name) => + throw new NotImplementedException(); + protected override IEnumerable EnumerateHeaders() => + throw new NotImplementedException(); + protected override bool TryGetHeader( + string name, + [NotNullWhen(true)] out string? value) => + throw new NotImplementedException(); + protected override bool TryGetHeaderValues( + string name, + [NotNullWhen(true)] out IEnumerable? values) => + throw new NotImplementedException(); +} diff --git a/docs/azure/sdk/snippets/unit-testing/NonLibrary/MockSecretClient.cs b/docs/azure/sdk/snippets/unit-testing/NonLibrary/MockSecretClient.cs new file mode 100644 index 0000000000000..1960b2e59268a --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/NonLibrary/MockSecretClient.cs @@ -0,0 +1,33 @@ +using Azure.Security.KeyVault.Secrets; +using Azure; +using NSubstitute.Routing.Handlers; + +namespace UnitTestingSampleApp.NonLibrary; + +public sealed class MockSecretClient : SecretClient +{ + AsyncPageable _pageable; + + // Allow a pageable to be passed in for mocking different responses + public MockSecretClient(AsyncPageable pageable) + { + _pageable = pageable; + } + + public override Response GetSecret( + string name, + string version = null, + CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public override Task> GetSecretAsync( + string name, + string version = null, + CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + // Return the pageable that was passed in + public override AsyncPageable GetPropertiesOfSecretsAsync + (CancellationToken cancellationToken = default) + => _pageable; +} diff --git a/docs/azure/sdk/snippets/unit-testing/NonLibrary/TestSnippets.cs b/docs/azure/sdk/snippets/unit-testing/NonLibrary/TestSnippets.cs new file mode 100644 index 0000000000000..39113b2943259 --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/NonLibrary/TestSnippets.cs @@ -0,0 +1,65 @@ +using Azure; +using Azure.Security.KeyVault.Secrets; + +namespace UnitTestingSampleApp.NonLibrary; + +public class TestSnippets +{ + public void ResponseTypeTSnippets() + { + // + KeyVaultSecret keyVaultSecret = SecretModelFactory.KeyVaultSecret( + new SecretProperties("secret"), "secretValue"); + + Response response = Response.FromValue(keyVaultSecret, new MockResponse()); + // + } + + public void PagingSnippets() + { + // + Page responsePage = Page.FromValues( + new[] { + new SecretProperties("secret1"), + new SecretProperties("secret2") + }, + continuationToken: null, + new MockResponse()); + // + + // + Page page1 = Page.FromValues( + new[] + { + new SecretProperties("secret1"), + new SecretProperties("secret2") + }, + "continuationToken", + new MockResponse()); + + Page page2 = Page.FromValues( + new[] + { + new SecretProperties("secret3"), + new SecretProperties("secret4") + }, + "continuationToken2", + new MockResponse()); + + Page lastPage = Page.FromValues( + new[] + { + new SecretProperties("secret5"), + new SecretProperties("secret6") + }, + continuationToken: null, + new MockResponse()); + + Pageable pageable = Pageable + .FromPages(new[] { page1, page2, lastPage }); + + AsyncPageable asyncPageable = AsyncPageable + .FromPages(new[] { page1, page2, lastPage }); + // + } +} diff --git a/docs/azure/sdk/snippets/unit-testing/UnitTestingSampleApp.csproj b/docs/azure/sdk/snippets/unit-testing/UnitTestingSampleApp.csproj new file mode 100644 index 0000000000000..c8611a5428938 --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/UnitTestingSampleApp.csproj @@ -0,0 +1,29 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/docs/azure/sdk/snippets/unit-testing/Usings.cs b/docs/azure/sdk/snippets/unit-testing/Usings.cs new file mode 100644 index 0000000000000..8c927eb747a6a --- /dev/null +++ b/docs/azure/sdk/snippets/unit-testing/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/docs/azure/sdk/unit-testing-mocking.md b/docs/azure/sdk/unit-testing-mocking.md new file mode 100644 index 0000000000000..a43e8c425b3c0 --- /dev/null +++ b/docs/azure/sdk/unit-testing-mocking.md @@ -0,0 +1,238 @@ +--- +title: Unit testing and mocking with the Azure SDK for .NET +description: Learn techniques and tools for unit testing and mocking the Azure SDK for .NET +ms.custom: devx-track-dotnet, engagement-fy23 +ms.date: 07/05/2023 +--- + +# Unit testing and mocking with the Azure SDK for .NET + +Unit testing is an important part of a sustainable development process that can improve code quality and prevent regressions or bugs in your apps. However, unit testing presents challenges when the code you're testing performs network calls, such as those made to Azure resources. Tests that run against live services can experience issues, such as latency that slows down test execution, dependencies on code outside of the isolated test, and issues with managing service state and costs every time the test is run. Instead of testing against live Azure services, replace the service clients with mocked or in-memory implementations. This avoids the above issues and lets developers focus on testing their application logic, independent from the network and service. + +In this article, you'll learn how to write unit tests for the Azure SDK for .NET that isolate your dependencies to make your tests more reliable. You'll also learn how to replace key components with in-memory test implementations to create fast and reliable unit tests, and see how to design your own classes to better support unit testing. This article includes examples that use [Moq](https://www.nuget.org/packages/moq/) and [NSubstitute](https://www.nuget.org/packages/nsubstitute/), which are popular mocking libraries for .NET. + +## Understand service clients + +A service client class is the main entry point for developers in an Azure SDK library and implements most of the logic to communicate with the Azure service. When unit testing service client classes, it's important to be able to create an instance of the client that behaves as expected without making any network calls. + +Each of the Azure SDK clients follows [mocking guidelines](https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-mocking) that allow their behavior to be overridden: + +* Each client offers at least one protected constructor to allow inheritance for testing. +* All public client members are virtual to allow overriding. + +> [!NOTE] +> The code examples in this article use types from the [Azure.Security.KeyVault.Secrets](https://www.nuget.org/packages/Azure.Security.KeyVault.Secrets/) library for the Azure Key Vault service. The concepts demonstrated in this article also apply to service clients from many other Azure services, such as Azure Storage or Azure Service Bus. + +To create a test service client, you can either use a mocking library or standard C# features such as inheritance. Mocking frameworks allow you to simplify the code that you must write to override member behavior (as well as other useful features that are beyond the scope of this article). + +# [Non-library](#tab/csharp) + +To create a test client instance using C# without a mocking library, inherit from the client type and override methods you are calling in your code with an implementation that returns a set of test objects. Most clients contain both synchronous and asynchronous methods for operations; override only the one your application code is calling. + +> [!NOTE] +> It can be cumbersome to manually define test classes, especially if you need to customize behavior differently for each test. Consider using a library like Moq or NSubstitute to streamline your testing. + +:::code language="csharp" source="snippets/unit-testing/NonLibrary/MockSecretClient.cs" ::: + +# [Moq](#tab/moq) + +:::code language="csharp" source="snippets/unit-testing/Moq/TestSnippets_Moq.cs" id="MockSecretClient" ::: + +# [NSubstitute](#tab/nsubstitute) + +:::code language="csharp" source="snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs" id="MockSecretClient" ::: + +--- + +### Service client input and output models + +Model types hold the data being sent and received from Azure services. There are three types of models: + +* **Input models** are intended to be created and passed as parameters to service methods by developers. They have one or more public constructors and writeable properties. +* **Output models** are only returned by the service and have neither public constructors nor writeable properties. +* **Round-trip models** are less common, but are returned by the service, modified, and used as an input. + +To create a test instance of an input model use one of the available public constructors and set the additional properties you need. + +```csharp +var secretProperties = new SecretProperties("secret") +{ + NotBefore = DateTimeOffset.Now +}; +``` + +To create instances of output models, a model factory is used. Azure SDK client libraries provide a static model factory class with a `ModelFactory` suffix in its name. The class contains a set of static methods to initialize the library's output model types. For example, the model factory for `SecretClient` is `SecretModelFactory`: + +```csharp +KeyVaultSecret keyVaultSecret = SecretModelFactory.KeyVaultSecret( + new SecretProperties("secret"), "secretValue"); +``` + +> [!NOTE] +> Some input models have read-only properties that are only populated when the model is returned by the service. In this case, a model factory method will be available that allows setting these properties. For example, . + +```csharp +// CreatedOn is a read-only property and can only be +// set via the model factory's SecretProperties method. +secretPropertiesWithCreatedOn = SecretModelFactory.SecretProperties( + name: "secret", createdOn: DateTimeOffset.Now); +``` + +## Explore response types + +The class is an abstract class that represents an HTTP response and is returned by most service client methods. You can create test `Response` instances using either a mocking library or standard C# inheritance. + +## [Non-library](#tab/csharp) + +The `Response` class is abstract, which means there are a lot of members to override. Consider using a library to streamline your approach. + +:::code language="csharp" source="snippets/unit-testing/NonLibrary/MockResponse.cs" ::: + +## [Moq](#tab/moq) + +:::code language="csharp" source="snippets/unit-testing/Moq/TestSnippets_Moq.cs" id="MockResponse" ::: + +# [NSubstitute](#tab/nsubstitute) + +:::code language="csharp" source="snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs" id="MockResponse" ::: + +--- + +Some services also support using the type, which is a class that contains a model and the HTTP response that returned it. To create a test instance of `Response`, use the static `Response.FromValue` method: + +## [Non-library](#tab/csharp) + +:::code language="csharp" source="snippets/unit-testing/NonLibrary/TestSnippets.cs" id="MockResponseT" ::: + +## [Moq](#tab/moq) + +:::code language="csharp" source="snippets/unit-testing/Moq/TestSnippets_Moq.cs" id="MockResponseT" ::: + +# [NSubstitute](#tab/nsubstitute) + +:::code language="csharp" source="snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs" id="MockResponseT" ::: + +--- + +### Explore paging + +The class is used as a building block in service methods that invoke operations returning results in multiple pages. The `Page` is rarely returned from APIs directly but is useful to create the `AsyncPageable` and `Pageable` instances in the next section. To create a `Page` instance, use the method, passing a list of items, a continuation token, and the `Response`. + +The `continuationToken` parameter is used to retrieve the next page from the service. For unit testing purposes, it should be set to `null` for the last page and should be non-empty for other pages. + +## [Non-library](#tab/csharp) + +:::code language="csharp" source="snippets/unit-testing/NonLibrary/TestSnippets.cs" id="SingleResponsePage" ::: + +## [Moq](#tab/moq) + +:::code language="csharp" source="snippets/unit-testing/Moq/TestSnippets_Moq.cs" id="SingleResponsePage" ::: + +# [NSubstitute](#tab/nsubstitute) + +:::code language="csharp" source="snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs" id="SingleResponsePage" ::: + +--- + + and are classes that represent collections of models returned by the service in pages. The only difference between them is that one is used with synchronous methods while the other is used with asynchronous methods. + +To create a test instance of `Pageable` or `AsyncPageable`, use the static method: + +## [Non-library](#tab/csharp) + +:::code language="csharp" source="snippets/unit-testing/NonLibrary/TestSnippets.cs" id="MultipleResponsePage" ::: + +## [Moq](#tab/moq) + +:::code language="csharp" source="snippets/unit-testing/Moq/TestSnippets_Moq.cs" id="MultipleResponsePage" ::: + +# [NSubstitute](#tab/nsubstitute) + +:::code language="csharp" source="snippets/unit-testing/NSubstitute/TestSnippets_NSubstitute.cs" id="MultipleResponsePage" ::: + +--- + +## Write a mocked unit test + +Suppose your app contains a class that finds the names of keys that will expire within a given amount of time. + +:::code language="csharp" source="snippets/unit-testing/AboutToExpireSecretsFinder.cs" ::: + +You want to test the following behaviors of the `AboutToExpireSecretFinder` to ensure they continue working as expected: + +* Secrets without an expiry date set aren't returned. +* Secrets with an expiry date closer to the current date than the threshold are returned. + +When unit testing you only want the unit tests to verify the application logic and not whether the Azure service or library works correctly. The following example tests the key behaviors using the popular [xUnit](https://www.nuget.org/packages/xunit) library: + +## [Non-library](#tab/csharp) + +:::code language="csharp" source="snippets/unit-testing/NonLibrary/AboutToExpireSecretsFinderTests.cs" ::: + +## [Moq](#tab/moq) + +:::code language="csharp" source="snippets/unit-testing/Moq/AboutToExpireSecretsFinderTests_Moq.cs" ::: + +# [NSubstitute](#tab/nsubstitute) + +:::code language="csharp" source="snippets/unit-testing/NSubstitute/AboutToExpireSecretsFinderTests_NSubstitute.cs" ::: + +--- + +## Refactor your types for testability + +Classes that need to be tested should be designed for [dependency injection](/dotnet/azure/sdk/dependency-injection), which allows the class to receive its dependencies instead of creating them internally. It was a seamless process to replace the `SecretClient` implementation in the example from the previous section because it was one of the constructor parameters. However, there may be classes in your code that create their own dependencies and are not easily testable, such as the following: + +```csharp +public class AboutToExpireSecretFinder +{ + public AboutToExpireSecretFinder(TimeSpan threshold) + { + _threshold = threshold; + _client = new SecretClient( + new Uri(Environment.GetEnvironmentVariable("KeyVaultUri")), + new DefaultAzureCredential()); + } +} +``` + +The simplest refactoring you can do to enable testing with dependency injection would be to expose the client as a parameter and run default creation code when no value is provided. This approach allows you to make the class testable while still retaining the flexibility of using the type without much ceremony. + +```csharp +public class AboutToExpireSecretFinder +{ + public AboutToExpireSecretFinder(TimeSpan threshold, SecretClient client = null) + { + _threshold = threshold; + _client = client ?? new SecretClient( + new Uri(Environment.GetEnvironmentVariable("KeyVaultUri")), + new DefaultAzureCredential()); + } +} +``` + +Another option is to move the dependency creation entirely into the calling code: + +```csharp +public class AboutToExpireSecretFinder +{ + public AboutToExpireSecretFinder(TimeSpan threshold, SecretClient client) + { + _threshold = threshold; + _client = client; + } +} + +var secretClient = new SecretClient( + new Uri(Environment.GetEnvironmentVariable("KeyVaultUri")), + new DefaultAzureCredential()); +var finder = new AboutToExpireSecretFinder(TimeSpan.FromDays(2), secretClient); +``` + +This approach is useful when you would like to consolidate the dependency creation and share the client between multiple consuming classes. + +## See also + +* [Dependency injection in .NET](/dotnet/core/extensions/dependency-injection) +* [Unit testing best practices](/dotnet/core/testing/unit-testing-best-practices) +* [Unit testing C# in .NET using dotnet test and xUnit](/dotnet/core/testing/unit-testing-with-dotnet-test) diff --git a/docs/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only.md b/docs/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only.md index 4797738c4861b..c80af14df5df3 100644 --- a/docs/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only.md +++ b/docs/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only.md @@ -54,8 +54,8 @@ From analysis of NuGet packages, we've observed that `System.Drawing.Common` is To use these APIs for cross-platform apps, migrate to one of the following libraries: -- [ImageSharp](https://sixlabors.com/products/imagesharp) - [SkiaSharp](https://github.com/mono/SkiaSharp) +- [ImageSharp](https://sixlabors.com/products/imagesharp) (tiered license) - [Microsoft.Maui.Graphics](/dotnet/maui/user-interface/graphics/) Alternatively, you can enable support for non-Windows platforms in .NET 6 by setting the `System.Drawing.EnableUnixSupport` [runtime configuration switch](../../../runtime-config/index.md) to `true` in the *runtimeconfig.json* file. diff --git a/docs/core/extensions/dependency-injection.md b/docs/core/extensions/dependency-injection.md index daa3a583be51e..767791b3c982f 100644 --- a/docs/core/extensions/dependency-injection.md +++ b/docs/core/extensions/dependency-injection.md @@ -3,7 +3,7 @@ title: Dependency injection description: Learn how to use dependency injection within your .NET apps. Discover how to registration services, define service lifetimes, and express dependencies in C#. author: IEvangelist ms.author: dapine -ms.date: 06/23/2023 +ms.date: 07/19/2023 ms.topic: overview --- @@ -61,7 +61,7 @@ This interface is implemented by a concrete type, `MessageWriter`: :::code language="csharp" source="snippets/configuration/dependency-injection/MessageWriter.cs"::: -The sample code registers the `IMessageWriter` service with the concrete type `MessageWriter`. The method registers the service with a scoped lifetime, the lifetime of a single request. [Service lifetimes](#service-lifetimes) are described later in this article. +The sample code registers the `IMessageWriter` service with the concrete type `MessageWriter`. The method registers the service with a singleton lifetime, the lifetime of a single request. [Service lifetimes](#service-lifetimes) are described later in this article. :::code language="csharp" source="snippets/configuration/dependency-injection/Program.cs" highlight="5-8"::: @@ -71,7 +71,7 @@ In the preceding code, the sample app: - Configures the services by registering: - The `Worker` as a hosted service. For more information, see [Worker Services in .NET](workers.md). - - The `IMessageWriter` interface as a scoped service with a corresponding implementation of the `MessageWriter` class. + - The `IMessageWriter` interface as a singleton service with a corresponding implementation of the `MessageWriter` class. - Builds the host and runs it. @@ -88,10 +88,10 @@ The implementation of the `IMessageWriter` interface can be improved by using th :::code language="csharp" source="snippets/configuration/dependency-injection/LoggingMessageWriter.cs"::: -The updated `AddScoped` method registers the new `IMessageWriter` implementation: +The updated `AddSingleton` method registers the new `IMessageWriter` implementation: ```csharp -builder.Services.AddScoped()); +builder.Services.AddSingleton()); ``` The (`builder`) type is part of the `Microsoft.Extensions.Hosting` NuGet package. @@ -122,7 +122,7 @@ public class Worker : BackgroundService while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); - await Task.Delay(1000, stoppingToken); + await Task.Delay(1_000, stoppingToken); } } } diff --git a/docs/core/extensions/snippets/configuration/dependency-injection/Program.cs b/docs/core/extensions/snippets/configuration/dependency-injection/Program.cs index 0ea2a33011994..ef79801a87fa5 100644 --- a/docs/core/extensions/snippets/configuration/dependency-injection/Program.cs +++ b/docs/core/extensions/snippets/configuration/dependency-injection/Program.cs @@ -3,7 +3,7 @@ HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); builder.Services.AddHostedService(); -builder.Services.AddScoped(); +builder.Services.AddSingleton(); using IHost host = builder.Build(); diff --git a/docs/core/tools/dotnet-build.md b/docs/core/tools/dotnet-build.md index 6542c8e78e1d9..182160d973fce 100644 --- a/docs/core/tools/dotnet-build.md +++ b/docs/core/tools/dotnet-build.md @@ -1,7 +1,7 @@ --- title: dotnet build command description: The dotnet build command builds a project and all of its dependencies. -ms.date: 06/23/2023 +ms.date: 07/19/2023 --- # dotnet build @@ -16,6 +16,7 @@ ms.date: 06/23/2023 ```dotnetcli dotnet build [|] [-a|--arch ] [-c|--configuration ] [-f|--framework ] + [--disable-build-servers] [--force] [--interactive] [--no-dependencies] [--no-incremental] [--no-restore] [--nologo] [--no-self-contained] [--os ] [-o|--output ] @@ -85,6 +86,8 @@ The project or solution file to build. If a project or solution file isn't speci [!INCLUDE [configuration](../../../includes/cli-configuration.md)] +[!INCLUDE [disable-build-servers](../../../includes/cli-disable-build-servers.md)] + - **`-f|--framework `** Compiles for a specific [framework](../../standard/frameworks.md). The framework must be defined in the [project file](../project-sdk/overview.md). Examples: `net7.0`, `net462`. diff --git a/docs/core/tools/dotnet-publish.md b/docs/core/tools/dotnet-publish.md index 8a28f232fd1fe..ed23d81dc38a1 100644 --- a/docs/core/tools/dotnet-publish.md +++ b/docs/core/tools/dotnet-publish.md @@ -1,7 +1,7 @@ --- title: dotnet publish command description: The dotnet publish command publishes a .NET project or solution to a directory. -ms.date: 12/02/2021 +ms.date: 07/19/2023 --- # dotnet publish @@ -15,7 +15,7 @@ ms.date: 12/02/2021 ```dotnetcli dotnet publish [|] [-a|--arch ] - [-c|--configuration ] + [-c|--configuration ] [--disable-build-servers] [-f|--framework ] [--force] [--interactive] [--manifest ] [--no-build] [--no-dependencies] [--no-restore] [--nologo] [-o|--output ] @@ -126,6 +126,8 @@ For more information, see the following resources: [!INCLUDE [configuration](../../../includes/cli-configuration.md)] +[!INCLUDE [disable-build-servers](../../../includes/cli-disable-build-servers.md)] + - **`-f|--framework `** Publishes the application for the specified [target framework](../../standard/frameworks.md). You must specify the target framework in the project file. diff --git a/docs/core/tools/dotnet-restore.md b/docs/core/tools/dotnet-restore.md index 3867be5855736..7af356e563bc2 100644 --- a/docs/core/tools/dotnet-restore.md +++ b/docs/core/tools/dotnet-restore.md @@ -1,7 +1,7 @@ --- title: dotnet restore command description: Learn how to restore dependencies and project-specific tools with the dotnet restore command. -ms.date: 05/16/2023 +ms.date: 07/19/2023 --- # dotnet restore @@ -14,7 +14,8 @@ ms.date: 05/16/2023 ## Synopsis ```dotnetcli -dotnet restore [] [--configfile ] [--disable-parallel] +dotnet restore [] [--configfile ] [--disable-build-servers] + [--disable-parallel] [-f|--force] [--force-evaluate] [--ignore-failed-sources] [--interactive] [--lock-file-path ] [--locked-mode] [--no-cache] [--no-dependencies] [--packages ] @@ -93,6 +94,8 @@ There are three specific settings that `dotnet restore` ignores: [!INCLUDE [configfile](../../../includes/cli-configfile.md)] +[!INCLUDE [disable-build-servers](../../../includes/cli-disable-build-servers.md)] + - **`--disable-parallel`** Disables restoring multiple projects in parallel. diff --git a/docs/framework/interop/configure-net-framework-based-com-components-for-reg.md b/docs/framework/interop/configure-net-framework-based-com-components-for-reg.md index c691f2df64750..bb0540e7aa13b 100644 --- a/docs/framework/interop/configure-net-framework-based-com-components-for-reg.md +++ b/docs/framework/interop/configure-net-framework-based-com-components-for-reg.md @@ -153,7 +153,7 @@ You can install an application manifest in the same directory as the COM applica 1. Create a resource script that contains the following statement: - `RT_MANIFEST 1 myManagedComp.manifest` + `1 RT_MANIFEST myManagedComp.manifest` In this statement, `myManagedComp.manifest` is the name of the component manifest being embedded. For this example, the script file name is `myresource.rc`. diff --git a/docs/fsharp/tools/fsharp-interactive/index.md b/docs/fsharp/tools/fsharp-interactive/index.md index 8027a478de7b5..7caa391811d98 100644 --- a/docs/fsharp/tools/fsharp-interactive/index.md +++ b/docs/fsharp/tools/fsharp-interactive/index.md @@ -11,6 +11,9 @@ F# Interactive (dotnet fsi) is used to run F# code interactively at the console, To run F# Interactive from the console, run `dotnet fsi`. You will find `dotnet fsi` in any .NET SDK. +> [!NOTE] +> If you intend to use F# interactive under .NET Framework runtime, you'll need the [Visual Studio Build Tools](https://visualstudio.microsoft.com/downloads/?q=build+tools) or an edition of Visual Studio installed, and invoke the `FsiAnyCPU.exe` command from a "Developer Command Prompt" or simply make `FsiAnyCPU.exe` available in the `PATH` environment variable. + For information about available command-line options, see [F# Interactive Options](../../language-reference/fsharp-interactive-options.md). ## Executing code directly in F# Interactive diff --git a/docs/fundamentals/code-analysis/quality-rules/ca3009.md b/docs/fundamentals/code-analysis/quality-rules/ca3009.md index 22194d24ff3a5..2204d6937c1c3 100644 --- a/docs/fundamentals/code-analysis/quality-rules/ca3009.md +++ b/docs/fundamentals/code-analysis/quality-rules/ca3009.md @@ -1,7 +1,7 @@ --- title: "CA3009: Review code for XML injection vulnerabilities (code analysis)" description: "Learn about code analysis rule CA3009: Review code for XML injection vulnerabilities" -ms.date: 07/21/2020 +ms.date: 07/19/2023 ms.topic: reference author: dotpaul ms.author: paulming @@ -38,139 +38,58 @@ This rule attempts to find input from HTTP requests reaching a raw XML write. ## How to fix violations -Don't write raw XML. Instead, use methods or properties that XML-encode their input. +To fix a violation, use one of the following techniques: -Or, XML-encode input before writing raw XML. - -Or, validate user input by using sanitizers for primitive type conversion and XML encoding. +- Don't write raw XML. Instead, use methods or properties that XML-encode their input. +- XML-encode input before writing raw XML. +- Validate user input by using sanitizers for primitive type conversion and XML encoding. ## When to suppress warnings Don't suppress warnings from this rule. -## Configure code to analyze - -Use the following options to configure which parts of your codebase to run this rule on. - -- [Exclude specific symbols](#exclude-specific-symbols) -- [Exclude specific types and their derived types](#exclude-specific-types-and-their-derived-types) +## Pseudo-code examples -You can configure these options for just this rule, for all rules it applies to, or for all rules in this category ([Security](security-warnings.md)) that it applies to. For more information, see [Code quality rule configuration options](../code-quality-rule-options.md). +### Violation -[!INCLUDE[excluded-symbol-names](../includes/excluded-symbol-names.md)] +In this example, the input is set to the property of the root element. Given input that contains valid XML, a malicious user can then completely alter the document. Notice that `alice` is no longer an allowed user after the user input is added to the document. -[!INCLUDE[excluded-type-names-with-derived-types](../includes/excluded-type-names-with-derived-types.md)] +:::code language="csharp" source="snippets/csharp/netfx/ca3009.cs" id="violation" highlight="12"::: -## Pseudo-code examples +:::code language="vb" source="snippets/vb/netfx/ca3009.vb" id="violation" highlight="11"::: -### Violation +If an attacker uses this for input: `some textoscar`, then the XML document will be: -```csharp -using System; -using System.Xml; - -public partial class WebForm : System.Web.UI.Page -{ - protected void Page_Load(object sender, EventArgs e) - { - string input = Request.Form["in"]; - XmlDocument d = new XmlDocument(); - XmlElement root = d.CreateElement("root"); - d.AppendChild(root); - - XmlElement allowedUser = d.CreateElement("allowedUser"); - root.AppendChild(allowedUser); - - allowedUser.InnerXml = "alice"; - - // If an attacker uses this for input: - // some textoscar - // Then the XML document will be: - // some textoscar - root.InnerXml = input; - } -} +```xml +some textoscar + ``` -```vb -Imports System -Imports System.Xml +### Solution -Public Partial Class WebForm - Inherits System.Web.UI.Page +To fix this violation, set the input to the property of the root element instead of the property. - Sub Page_Load(sender As Object, e As EventArgs) - Dim input As String = Request.Form("in") - Dim d As XmlDocument = New XmlDocument() - Dim root As XmlElement = d.CreateElement("root") - d.AppendChild(root) +:::code language="csharp" source="snippets/csharp/netfx/ca3009.cs" id="fix" highlight="12"::: - Dim allowedUser As XmlElement = d.CreateElement("allowedUser") - root.AppendChild(allowedUser) +:::code language="vb" source="snippets/vb/netfx/ca3009.vb" id="fix" highlight="11"::: - allowedUser.InnerXml = "alice" +If an attacker uses this for input: `some textoscar`, then the XML document will be: - ' If an attacker uses this for input: - ' some textoscar - ' Then the XML document will be: - ' some textoscar - root.InnerXml = input - End Sub -End Class +```xml +some text<allowedUser>oscar</allowedUser> +alice + ``` -### Solution - -```csharp -using System; -using System.Xml; - -public partial class WebForm : System.Web.UI.Page -{ - protected void Page_Load(object sender, EventArgs e) - { - string input = Request.Form["in"]; - XmlDocument d = new XmlDocument(); - XmlElement root = d.CreateElement("root"); - d.AppendChild(root); - - XmlElement allowedUser = d.CreateElement("allowedUser"); - root.AppendChild(allowedUser); - - allowedUser.InnerText = "alice"; - - // If an attacker uses this for input: - // some textoscar - // Then the XML document will be: - // <allowedUser>oscar</allowedUser>some textalice - root.InnerText = input; - } -} -``` - -```vb -Imports System -Imports System.Xml +## Configure code to analyze -Public Partial Class WebForm - Inherits System.Web.UI.Page +Use the following options to configure which parts of your codebase to run this rule on. - Sub Page_Load(sender As Object, e As EventArgs) - Dim input As String = Request.Form("in") - Dim d As XmlDocument = New XmlDocument() - Dim root As XmlElement = d.CreateElement("root") - d.AppendChild(root) +- [Exclude specific symbols](#exclude-specific-symbols) +- [Exclude specific types and their derived types](#exclude-specific-types-and-their-derived-types) - Dim allowedUser As XmlElement = d.CreateElement("allowedUser") - root.AppendChild(allowedUser) +You can configure these options for just this rule, for all rules it applies to, or for all rules in this category ([Security](security-warnings.md)) that it applies to. For more information, see [Code quality rule configuration options](../code-quality-rule-options.md). - allowedUser.InnerText = "alice" +[!INCLUDE[excluded-symbol-names](../includes/excluded-symbol-names.md)] - ' If an attacker uses this for input: - ' some textoscar - ' Then the XML document will be: - ' <allowedUser>oscar</allowedUser>some textalice - root.InnerText = input - End Sub -End Class -``` +[!INCLUDE[excluded-type-names-with-derived-types](../includes/excluded-type-names-with-derived-types.md)] diff --git a/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/netfx/ca3009.cs b/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/netfx/ca3009.cs new file mode 100644 index 0000000000000..9a97376520eee --- /dev/null +++ b/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/netfx/ca3009.cs @@ -0,0 +1,40 @@ +using System; +using System.Xml; + +public partial class WebForm1 : System.Web.UI.Page +{ + // + protected void Page_Load(object sender, EventArgs e) + { + XmlDocument d = new XmlDocument(); + XmlElement root = d.CreateElement("root"); + d.AppendChild(root); + + XmlElement allowedUser = d.CreateElement("allowedUser"); + root.AppendChild(allowedUser); + allowedUser.InnerXml = "alice"; + + string input = Request.Form["in"]; + root.InnerXml = input; + } + // +} + +public partial class WebForm2 : System.Web.UI.Page +{ + // + protected void Page_Load(object sender, EventArgs e) + { + XmlDocument d = new XmlDocument(); + XmlElement root = d.CreateElement("root"); + d.AppendChild(root); + + XmlElement allowedUser = d.CreateElement("allowedUser"); + root.AppendChild(allowedUser); + allowedUser.InnerText = "alice"; + + string input = Request.Form["in"]; + root.InnerText = input; + } + // +} diff --git a/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/netfx/project.csproj b/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/netfx/project.csproj new file mode 100644 index 0000000000000..cdba31d53c4b9 --- /dev/null +++ b/docs/fundamentals/code-analysis/quality-rules/snippets/csharp/netfx/project.csproj @@ -0,0 +1,12 @@ + + + + Library + net48 + + + + + + + diff --git a/docs/fundamentals/code-analysis/quality-rules/snippets/vb/netfx/ca3009.vb b/docs/fundamentals/code-analysis/quality-rules/snippets/vb/netfx/ca3009.vb new file mode 100644 index 0000000000000..fc92c93e7ca0f --- /dev/null +++ b/docs/fundamentals/code-analysis/quality-rules/snippets/vb/netfx/ca3009.vb @@ -0,0 +1,41 @@ + +Imports System +Imports System.Xml + +Public Partial Class WebForm1 + Inherits System.Web.UI.Page + + ' + Sub Page_Load(sender As Object, e As EventArgs) + Dim d As XmlDocument = New XmlDocument() + Dim root As XmlElement = d.CreateElement("root") + d.AppendChild(root) + + Dim allowedUser As XmlElement = d.CreateElement("allowedUser") + root.AppendChild(allowedUser) + allowedUser.InnerXml = "alice" + + Dim input As String = Request.Form("in") + root.InnerXml = input + End Sub + ' +End Class + +Public Partial Class WebForm2 + Inherits System.Web.UI.Page + + ' + Sub Page_Load(sender As Object, e As EventArgs) + Dim d As XmlDocument = New XmlDocument() + Dim root As XmlElement = d.CreateElement("root") + d.AppendChild(root) + + Dim allowedUser As XmlElement = d.CreateElement("allowedUser") + root.AppendChild(allowedUser) + allowedUser.InnerText = "alice" + + Dim input As String = Request.Form("in") + root.InnerText = input + End Sub + ' +End Class diff --git a/docs/fundamentals/code-analysis/quality-rules/snippets/vb/netfx/project.vbproj b/docs/fundamentals/code-analysis/quality-rules/snippets/vb/netfx/project.vbproj new file mode 100644 index 0000000000000..1216668729024 --- /dev/null +++ b/docs/fundamentals/code-analysis/quality-rules/snippets/vb/netfx/project.vbproj @@ -0,0 +1,12 @@ + + + + Library + net48 + + + + + + + diff --git a/docs/fundamentals/code-analysis/style-rules/ide0120.md b/docs/fundamentals/code-analysis/style-rules/ide0120.md new file mode 100644 index 0000000000000..60899ed97750b --- /dev/null +++ b/docs/fundamentals/code-analysis/style-rules/ide0120.md @@ -0,0 +1,83 @@ +--- +title: "IDE0120: Simplify LINQ expression" +description: "Learn about code analysis rule IDE0120: Simplify LINQ expression" +ms.date: 07/19/2023 +ms.topic: reference +f1_keywords: +- IDE0120 +helpviewer_keywords: +- IDE0120 +dev_langs: +- CSharp +--- +# Simplify LINQ expression (IDE0120) + +| Property | Value | +| ------------------------ | -------------------------- | +| **Rule ID** | IDE0120 | +| **Title** | Simplify LINQ expression | +| **Category** | Style | +| **Subcategory** | Unnecessary code rules | +| **Applicable languages** | C# and Visual Basic | + +## Overview + +This rule flags overly complex LINQ expressions, specifically expressions that call followed by one of the following methods: + +- +- +- +- +- +- +- +- + +Such expressions can be simplified by removing the call to and instead calling an overload of `Any()`, `Count()`, `First()`, `FirstOrDefault()`, `Last()`, `LastOrDefault()`, `Single`, or `SingleOrDefault()` that accepts a predicate function to filter the elements. + +## Options + +This rule has no associated code-style options. + +## Example + +```csharp +// Code with violations. +IEnumerable words = new List { "hello", "world", "!" }; +var result = words.Where(x => x.Equals("hello")).Any(); + +// Fixed code. +IEnumerable words = new List { "hello", "world", "!" }; +var result = words.Any(x => x.Equals("hello")); +``` + +## Suppress a warning + +If you want to suppress only a single violation, add preprocessor directives to your source file to disable and then re-enable the rule. + +```csharp +#pragma warning disable IDE0120 +// The code that's violating the rule is on this line. +#pragma warning restore IDE0120 +``` + +To disable the rule for a file, folder, or project, set its severity to `none` in the [configuration file](../configuration-files.md). + +```ini +[*.{cs,vb}] +dotnet_diagnostic.IDE0120.severity = none +``` + +To disable all of the code-style rules, set the severity for the category `Style` to `none` in the [configuration file](../configuration-files.md). + +```ini +[*.{cs,vb}] +dotnet_analyzer_diagnostic.category-Style.severity = none +``` + +For more information, see [How to suppress code analysis warnings](../suppress-warnings.md). + +## See also + +- [Unnecessary code rules](unnecessary-code-rules.md) +- [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0130.md b/docs/fundamentals/code-analysis/style-rules/ide0130.md index 1ab515c05149d..d8deb4cebd5b7 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0130.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0130.md @@ -9,8 +9,6 @@ f1_keywords: helpviewer_keywords: - IDE0130 - dotnet_style_namespace_match_folder -author: gewarren -ms.author: gewarren dev_langs: - CSharp - VB diff --git a/docs/fundamentals/code-analysis/style-rules/ide0200.md b/docs/fundamentals/code-analysis/style-rules/ide0200.md new file mode 100644 index 0000000000000..e2b4f3b93643b --- /dev/null +++ b/docs/fundamentals/code-analysis/style-rules/ide0200.md @@ -0,0 +1,89 @@ +--- +title: "IDE0200: Remove unnecessary lambda expression" +description: "Learn about code analysis rule IDE0200: Remove unnecessary lambda expression" +ms.date: 07/19/2023 +ms.topic: reference +f1_keywords: +- IDE0200 +helpviewer_keywords: +- IDE0200 +dev_langs: +- CSharp +--- +# Remove unnecessary lambda expression (IDE0200) + +| Property | Value | +|--------------------------|-----------------------------------------------| +| **Rule ID** | IDE0200 | +| **Title** | Remove unnecessary lambda expression | +| **Category** | Style | +| **Subcategory** | Unnecessary code rules | +| **Applicable languages** | C# 11+ | +| **Options** | `csharp_style_prefer_method_group_conversion` | + +## Overview + +This rule flags the use of a lambda expression where it's unnecessary. Lambda expressions might be unnecessary when the following are all true: + +- The expression includes a method invocation. +- The lambda expression has the same number and order of parameters as the method invocation. +- The method invocation has no side effects. +- The lambda expression isn't assigned to a non-delegate type. +- If the invocation is a generic method, the type arguments are supplied. +- The invoked method's return type can be converted to the lambda expression's return type. + +## Options + +Options specify the behavior that you want the rule to enforce. For information about configuring options, see [Option format](language-rules.md#option-format). + +### csharp_style_prefer_method_group_conversion + +| Property | Value | Description | +|--------------------------|---------------------------------------------|---------------------------------------------------------| +| **Option name** | csharp_style_prefer_method_group_conversion | | +| **Option values** | `true` | Prefer to convert a lambda expression to a method call. | +| | `false` | Disables the rule. | +| **Default option value** | `true` | | + +## Example + +```csharp +// Code with violations. +bool IsEven(int x) => x % 2 == 0; +_ = new[] { 1, 2, 3 }.Where(n => IsEven(n)); + +// Fixed code. +bool IsEven(int x) => x % 2 == 0; +_ = new[] { 1, 2, 3 }.Where(IsEven); +``` + +## Suppress a warning + +If you want to suppress only a single violation, add preprocessor directives to your source file to disable and then re-enable the rule. + +```csharp +#pragma warning disable IDE0200 +// The code that's violating the rule is on this line. +#pragma warning restore IDE0200 +``` + +To disable the rule for a file, folder, or project, set its severity to `none` in the [configuration file](../configuration-files.md). + +```ini +[*.{cs,vb}] +dotnet_diagnostic.IDE0200.severity = none +``` + +To disable all of the code-style rules, set the severity for the category `Style` to `none` in the [configuration file](../configuration-files.md). + +```ini +[*.{cs,vb}] +dotnet_analyzer_diagnostic.category-Style.severity = none +``` + +For more information, see [How to suppress code analysis warnings](../suppress-warnings.md). + +## See also + +- [Unnecessary code rules](unnecessary-code-rules.md) +- [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/index.md b/docs/fundamentals/code-analysis/style-rules/index.md index 6ba6fb1db272f..20a39b7811966 100644 --- a/docs/fundamentals/code-analysis/style-rules/index.md +++ b/docs/fundamentals/code-analysis/style-rules/index.md @@ -119,6 +119,7 @@ The following table list all the code-style rules by ID and [options](../code-st > | [IDE0090](ide0090.md) | Simplify `new` expression | [csharp_style_implicit_object_creation_when_type_is_apparent](ide0090.md#csharp_style_implicit_object_creation_when_type_is_apparent) | > | [IDE0100](ide0100.md) | Remove unnecessary equality operator | | > | [IDE0110](ide0110.md) | Remove unnecessary discard | | +> | [IDE0120](ide0120.md) | Simplify LINQ expression | | > | [IDE0130](ide0130.md) | Namespace does not match folder structure | [dotnet_style_namespace_match_folder](ide0130.md#dotnet_style_namespace_match_folder) | > | [IDE0140](ide0140.md) | Simplify object creation | [visual_basic_style_prefer_simplified_object_creation](ide0140.md#visual_basic_style_prefer_simplified_object_creation) | > | [IDE0150](ide0150.md) | Prefer `null` check over type check | [csharp_style_prefer_null_check_over_type_check](ide0150.md#csharp_style_prefer_null_check_over_type_check) | @@ -126,6 +127,7 @@ The following table list all the code-style rules by ID and [options](../code-st > | [IDE0161](ide0160-ide0161.md) | Use file-scoped namespace | [csharp_style_namespace_declarations](ide0160-ide0161.md#csharp_style_namespace_declarations) | > | [IDE0170](ide0170.md) | Simplify property pattern | [csharp_style_prefer_extended_property_pattern](ide0170.md#csharp_style_prefer_extended_property_pattern) | > | [IDE0180](ide0180.md) | Use tuple to swap values | [csharp_style_prefer_tuple_swap](ide0180.md#csharp_style_prefer_tuple_swap) | +> | [IDE0200](ide0200.md) | Remove unnecessary lambda expression | [csharp_style_prefer_method_group_conversion](ide0200.md#csharp_style_prefer_method_group_conversion) | > | [IDE1005](ide1005.md) | Use conditional delegate call | [csharp_style_conditional_delegate_call](ide1005.md#csharp_style_conditional_delegate_call) | > | [IDE1006](naming-rules.md) | Naming styles | | diff --git a/docs/fundamentals/code-analysis/style-rules/unnecessary-code-rules.md b/docs/fundamentals/code-analysis/style-rules/unnecessary-code-rules.md index 0033a2b885347..fc895fafc944f 100644 --- a/docs/fundamentals/code-analysis/style-rules/unnecessary-code-rules.md +++ b/docs/fundamentals/code-analysis/style-rules/unnecessary-code-rules.md @@ -35,7 +35,9 @@ The rules in this section concern the following unnecessary code rules: - [Remove 'ByVal' (IDE0081)](ide0081.md) - Visual Basic only. - [Remove unnecessary equality operator (IDE0100)](ide0100.md) - [Remove unnecessary discard (IDE0110)](ide0110.md) - C# only. +- [Simplify LINQ expression (IDE0120)](ide0120.md) - [Simplify object creation (IDE0140)](ide0140.md) - Visual Basic only. +- [Remove unnecessary lambda expression (IDE0200)](ide0200.md) Some of these rules have options to configure the rule behavior. diff --git a/docs/navigate/tools-diagnostics/toc.yml b/docs/navigate/tools-diagnostics/toc.yml index f0875320d9a78..cd8d947f9f112 100644 --- a/docs/navigate/tools-diagnostics/toc.yml +++ b/docs/navigate/tools-diagnostics/toc.yml @@ -1342,8 +1342,12 @@ items: href: ../../fundamentals/code-analysis/style-rules/ide0100.md - name: IDE0110 href: ../../fundamentals/code-analysis/style-rules/ide0110.md + - name: IDE0120 + href: ../../fundamentals/code-analysis/style-rules/ide0120.md - name: IDE0140 href: ../../fundamentals/code-analysis/style-rules/ide0140.md + - name: IDE0200 + href: ../../fundamentals/code-analysis/style-rules/ide0200.md - name: Miscellaneous rules items: - name: Overview diff --git a/docs/standard/garbage-collection/implementing-dispose.md b/docs/standard/garbage-collection/implementing-dispose.md index 7ec568c2f3107..61ddf9fa4b9d5 100644 --- a/docs/standard/garbage-collection/implementing-dispose.md +++ b/docs/standard/garbage-collection/implementing-dispose.md @@ -1,7 +1,7 @@ --- title: "Implement a Dispose method" description: In this article, learn to implement the Dispose method, which releases unmanaged resources used by your code in .NET. -ms.date: 02/16/2023 +ms.date: 07/20/2023 dev_langs: - "csharp" - "vb" @@ -13,11 +13,11 @@ ms.topic: how-to # Implement a Dispose method -The method is primarily implemented to release unmanaged resources. When working with instance members that are implementations, it's common to cascade calls. There are additional reasons for implementing , for example, to free memory that was allocated, remove an item that was added to a collection, or signal the release of a lock that was acquired. +The method is primarily implemented to release unmanaged resources. When working with instance members that are implementations, it's common to cascade calls. There are other reasons for implementing , for example, to free memory that was allocated, remove an item that was added to a collection, or signal the release of a lock that was acquired. -The [.NET garbage collector](index.md) does not allocate or release unmanaged memory. The pattern for disposing an object, referred to as the dispose pattern, imposes order on the lifetime of an object. The dispose pattern is used for objects that implement the interface. This pattern is common when interacting with file and pipe handles, registry handles, wait handles, or pointers to blocks of unmanaged memory, because the garbage collector is unable to reclaim unmanaged objects. +The [.NET garbage collector](index.md) doesn't allocate or release unmanaged memory. The pattern for disposing an object, referred to as the dispose pattern, imposes order on the lifetime of an object. The dispose pattern is used for objects that implement the interface. This pattern is common when interacting with file and pipe handles, registry handles, wait handles, or pointers to blocks of unmanaged memory, because the garbage collector is unable to reclaim unmanaged objects. -To help ensure that resources are always cleaned up appropriately, a method should be idempotent, such that it is callable multiple times without throwing an exception. Furthermore, subsequent invocations of should do nothing. +To help ensure that resources are always cleaned up appropriately, a method should be idempotent, such that it's callable multiple times without throwing an exception. Furthermore, subsequent invocations of should do nothing. The code example provided for the method shows how garbage collection can cause a finalizer to run while an unmanaged reference to the object or its members is still in use. It may make sense to utilize to make the object ineligible for garbage collection from the start of the current routine to the point where this method is called. @@ -29,7 +29,7 @@ Writing code for an object's finalizer is a complex task that can cause problems A is an abstract managed type that wraps an that identifies an unmanaged resource. On Windows it might identify a handle, and on Unix, a file descriptor. The `SafeHandle` provides all of the logic necessary to ensure that this resource is released once and only once, either when the `SafeHandle` is disposed of or when all references to the `SafeHandle` have been dropped and the `SafeHandle` instance is finalized. -The is an abstract base class. Derived classes provide specific instances for different kinds of handle. These derived classes validate what values for the are considered invalid and how to actually free the handle. For example, derives from `SafeHandle` to wrap `IntPtrs` that identify open file handles/descriptors, and overrides its method to close it (via the `close` function on Unix or `CloseHandle` function on Windows). Most APIs in .NET libraries that create an unmanaged resource will wrap it in a `SafeHandle` and return that `SafeHandle` to you as needed, rather than handing back the raw pointer. In situations where you interact with an unmanaged component and get an `IntPtr` for an unmanaged resource, you can create your own `SafeHandle` type to wrap it. As a result, few non-`SafeHandle` types need to implement finalizers. Most disposable pattern implementations only end up wrapping other managed resources, some of which may be `SafeHandle` objects. +The is an abstract base class. Derived classes provide specific instances for different kinds of handle. These derived classes validate what values for the are considered invalid and how to actually free the handle. For example, derives from `SafeHandle` to wrap `IntPtrs` that identify open file handles/descriptors, and overrides its method to close it (via the `close` function on Unix or `CloseHandle` function on Windows). Most APIs in .NET libraries that create an unmanaged resource wraps it in a `SafeHandle` and return that `SafeHandle` to you as needed, rather than handing back the raw pointer. In situations where you interact with an unmanaged component and get an `IntPtr` for an unmanaged resource, you can create your own `SafeHandle` type to wrap it. As a result, few non-`SafeHandle` types need to implement finalizers. Most disposable pattern implementations only end up wrapping other managed resources, some of which may be `SafeHandle` objects. The following derived classes in the namespace provide safe handles. @@ -43,7 +43,7 @@ The following derived classes in the namespac ## Dispose() and Dispose(bool) -The interface requires the implementation of a single parameterless method, . Also, any non-sealed class should have an additional `Dispose(bool)` overload method. +The interface requires the implementation of a single parameterless method, . Also, any non-sealed class should have an `Dispose(bool)` overload method. Method signatures are: @@ -52,12 +52,12 @@ Method signatures are: ### The Dispose() method -Because the `public`, non-virtual (`NotOverridable` in Visual Basic), parameterless `Dispose` method is called when it is no longer needed (by a consumer of the type), its purpose is to free unmanaged resources, perform general cleanup, and to indicate that the finalizer, if one is present, doesn't have to run. Freeing the actual memory associated with a managed object is always the domain of the [garbage collector](index.md). Because of this, it has a standard implementation: +Because the `public`, non-virtual (`NotOverridable` in Visual Basic), parameterless `Dispose` method is called when it's no longer needed (by a consumer of the type), its purpose is to free unmanaged resources, perform general cleanup, and to indicate that the finalizer, if one is present, doesn't have to run. Freeing the actual memory associated with a managed object is always the domain of the [garbage collector](index.md). Because of this, it has a standard implementation: :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/Disposable.cs" id="Dispose"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/Disposable.vb" id="Dispose"::: -The `Dispose` method performs all object cleanup, so the garbage collector no longer needs to call the objects' override. Therefore, the call to the method prevents the garbage collector from running the finalizer. If the type has no finalizer, the call to has no effect. Note that the actual cleanup is performed by the `Dispose(bool)` method overload. +The `Dispose` method performs all object cleanup, so the garbage collector no longer needs to call the objects' override. Therefore, the call to the method prevents the garbage collector from running the finalizer. If the type has no finalizer, the call to has no effect. The actual cleanup is performed by the `Dispose(bool)` method overload. ### The Dispose(bool) method overload @@ -77,9 +77,9 @@ The body of the method consists of three blocks of code: - **Managed objects that implement .** The conditional block can be used to call their implementation (cascade dispose). If you have used a derived class of to wrap your unmanaged resource, you should call the implementation here. - - **Managed objects that consume large amounts of memory or consume scarce resources.** Assign large managed object references to `null` to make them more likely to be unreachable. This releases them faster than if they were reclaimed non-deterministically. + - **Managed objects that consume large amounts of memory or consume scarce resources.** Assign large managed object references to `null` to make them more likely to be unreachable. This releases them faster than if they were reclaimed nondeterministically. -If the method call comes from a finalizer, only the code that frees unmanaged resources should execute. The implementer is responsible for ensuring that the false path doesn't interact with managed objects that may have been disposed. This is important because the order in which the garbage collector disposes managed objects during finalization is non-deterministic. +If the method call comes from a finalizer, only the code that frees unmanaged resources should execute. The implementer is responsible for ensuring that the false path doesn't interact with managed objects that may have been disposed. This is important because the order in which the garbage collector disposes managed objects during finalization is nondeterministic. ## Cascade dispose calls @@ -104,7 +104,7 @@ All non-sealed classes (or Visual Basic classes not modified as `NotInheritable` > [!IMPORTANT] > It's possible for a base class to only reference managed objects and implement the dispose pattern. In these cases, a finalizer is unnecessary. A finalizer is only required if you directly reference unmanaged resources. -Here's an example of the general pattern for implementing the dispose pattern for a base class that uses a safe handle. +Here's a general example of implementing the dispose pattern for a base class that uses a safe handle. :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb"::: diff --git a/includes/cli-disable-build-servers.md b/includes/cli-disable-build-servers.md new file mode 100644 index 0000000000000..a964b8c4b5cec --- /dev/null +++ b/includes/cli-disable-build-servers.md @@ -0,0 +1,9 @@ +--- +author: tdykstra +ms.author: tdykstra +ms.date: 07/20/2023 +ms.topic: include +--- +- **`--disable-build-servers`** + + Forces the command to ignore any persistent build servers. This option provides a consistent way to disable all use of build caching, which forces a build from scratch. A build that doesn't rely on caches is useful when the caches might be corrupted or incorrect for some reason. Available since .NET 7 SDK. diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs index c9756aaf83b10..9a68d1b29e500 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs @@ -2,16 +2,20 @@ using System; using System.Runtime.InteropServices; -class BaseClassWithSafeHandle : IDisposable +public class BaseClassWithSafeHandle : IDisposable { // To detect redundant calls private bool _disposedValue; // Instantiate a SafeHandle instance. - private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true); + private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true); // Public implementation of Dispose pattern callable by consumers. - public void Dispose() => Dispose(true); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) @@ -20,7 +24,8 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _safeHandle.Dispose(); + _safeHandle?.Dispose(); + _safeHandle = null; } _disposedValue = true; diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs index 37b9e3a4acc32..4b45bdef29187 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base2.cs @@ -1,6 +1,6 @@ using System; -class BaseClassWithFinalizer : IDisposable +public class BaseClassWithFinalizer : IDisposable { // To detect redundant calls private bool _disposedValue; diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/calling2.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/calling2.cs index f853e786c2c33..af723fc53c423 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/calling2.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/calling2.cs @@ -14,8 +14,8 @@ public WordCountCleanup(string filename) FullName = filename; - var txt = string.Empty; - StreamReader streamReader = null; + string txt = string.Empty; + StreamReader? streamReader = null; try { streamReader = new StreamReader(filename); diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs index 5835233c863f5..25794ca4c3147 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived1.cs @@ -2,13 +2,13 @@ using System; using System.Runtime.InteropServices; -class DerivedClassWithSafeHandle : BaseClassWithSafeHandle +public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle { // To detect redundant calls private bool _disposedValue; // Instantiate a SafeHandle instance. - private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true); + private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true); // Protected implementation of Dispose pattern. protected override void Dispose(bool disposing) @@ -17,7 +17,8 @@ protected override void Dispose(bool disposing) { if (disposing) { - _safeHandle.Dispose(); + _safeHandle?.Dispose(); + _safeHandle = null; } _disposedValue = true; diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs index 8eec1fa25eadf..0966b3e633a08 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/derived2.cs @@ -1,9 +1,9 @@ -class DerivedClassWithFinalizer : BaseClassWithFinalizer +public class DerivedClassWithFinalizer : BaseClassWithFinalizer { // To detect redundant calls private bool _disposedValue; - ~DerivedClassWithFinalizer() => this.Dispose(false); + ~DerivedClassWithFinalizer() => Dispose(false); // Protected implementation of Dispose pattern. protected override void Dispose(bool disposing) diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb index 3e367a57f73bb..e0f548ed5b046 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb @@ -1,11 +1,14 @@ Imports Microsoft.Win32.SafeHandles Imports System.Runtime.InteropServices -Class BaseClassWithSafeHandle : Implements IDisposable - ' Flag: Has Dispose already been called? - Dim disposed As Boolean = False +Public Class BaseClassWithSafeHandle + Implements IDisposable + + ' To detect redundant calls + Private _disposedValue As Boolean + ' Instantiate a SafeHandle instance. - Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True) + Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True) ' Public implementation of Dispose pattern callable by consumers. Public Sub Dispose() _ @@ -15,13 +18,15 @@ Class BaseClassWithSafeHandle : Implements IDisposable End Sub ' Protected implementation of Dispose pattern. - Protected Overridable Sub Dispose(disposing As Boolean) - If disposed Then Return + Protected Overridable Sub Dispose(ByVal disposing As Boolean) + If Not _disposedValue Then - If disposing Then - handle.Dispose() - End If + If disposing Then + _safeHandle?.Dispose() + _safeHandle = Nothing + End If - disposed = True + _disposedValue = True + End If End Sub End Class diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb index ccfad4bcd24e9..54fb833eb5cba 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base2.vb @@ -1,6 +1,12 @@ -Class BaseClassWithFinalizer : Implements IDisposable - ' Flag: Has Dispose already been called? - Dim disposed As Boolean = False +Public Class BaseClassWithFinalizer + Implements IDisposable + + ' To detect redundant calls + Private _disposedValue As Boolean + + Protected Overrides Sub Finalize() + Dispose(False) + End Sub ' Public implementation of Dispose pattern callable by consumers. Public Sub Dispose() _ @@ -10,20 +16,16 @@ End Sub ' Protected implementation of Dispose pattern. - Protected Overridable Sub Dispose(disposing As Boolean) - If disposed Then Return + Protected Overridable Sub Dispose(ByVal disposing As Boolean) + If Not _disposedValue Then - If disposing Then - ' Dispose managed objects that implement IDisposable. - ' Assign null to managed objects that consume large amounts of memory or consume scarce resources. - End If + If disposing Then + ' TODO: dispose managed state (managed objects) + End If - ' Free any unmanaged objects here. - ' - disposed = True - End Sub - - Protected Overrides Sub Finalize() - Dispose(False) + ' TODO free unmanaged resources (unmanaged objects) And override finalizer + ' TODO: set large fields to null + _disposedValue = True + End If End Sub End Class diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb index 160c93b966061..83a005f785382 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived1.vb @@ -1,25 +1,25 @@ Imports Microsoft.Win32.SafeHandles Imports System.Runtime.InteropServices -Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle - ' Flag: Has Dispose already been called? - Dim disposed As Boolean = False +Public Class DerivedClassWithSafeHandle + Inherits BaseClassWithSafeHandle + + ' To detect redundant calls + Private _disposedValue As Boolean + ' Instantiate a SafeHandle instance. - Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True) + Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True) - ' Protected implementation of Dispose pattern. - Protected Overrides Sub Dispose(disposing As Boolean) - If disposed Then Return + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + If Not _disposedValue Then - If disposing Then - handle.Dispose() - ' Free any other managed objects here. - ' - End If + If disposing Then + _safeHandle?.Dispose() + _safeHandle = Nothing + End If - ' Free any unmanaged objects here. - ' - disposed = True + _disposedValue = True + End If ' Call base class implementation. MyBase.Dispose(disposing) diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb index 1f8e5c4dfff44..ed6e5b13b1ac1 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/derived2.vb @@ -1,26 +1,27 @@ -Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer - ' Flag: Has Dispose already been called? - Dim disposed As Boolean = False +Public Class DerivedClassWithFinalizer + Inherits BaseClassWithFinalizer + + ' To detect redundant calls + Private _disposedValue As Boolean + + Protected Overrides Sub Finalize() + Dispose(False) + End Sub ' Protected implementation of Dispose pattern. - Protected Overrides Sub Dispose(disposing As Boolean) - If disposed Then Return + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + If Not _disposedValue Then - If disposing Then - ' Dispose managed objects that implement IDisposable. - ' Assign null to managed objects that consume large amounts of memory or consume scarce resources. - End If + If disposing Then + ' TODO: dispose managed state (managed objects). + End If - ' Free any unmanaged objects here. - ' - disposed = True + ' TODO free unmanaged resources (unmanaged objects) And override a finalizer below. + ' TODO: set large fields to null. + _disposedValue = True + End If ' Call the base class implementation. MyBase.Dispose(disposing) End Sub - - Protected Overrides Sub Finalize() - Dispose(False) - End Sub End Class -