diff --git a/.github/workflows/itests.yml b/.github/workflows/itests.yml index d06c12cd5..4dcdfb951 100644 --- a/.github/workflows/itests.yml +++ b/.github/workflows/itests.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - dotnet-version: ['6.0', '7.0', '8.0'] + dotnet-version: ['6.0', '7.0', '8.0', '9.0'] include: - dotnet-version: '6.0' display-name: '.NET 6.0' @@ -37,6 +37,11 @@ jobs: framework: 'net8' prefix: 'net8' install-version: '8.0.x' + - dotnet-version: '9.0' + display-name: '.NET 9.0' + framework: 'net9' + prefix: 'net9' + install-version: '9.0.x' env: NUPKG_OUTDIR: bin/Release/nugets GOVER: 1.20.3 @@ -103,14 +108,22 @@ jobs: - name: Parse release version run: python ./.github/scripts/get_release_version.py - name: Setup ${{ matrix.display-name }} - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: ${{ matrix.install-version }} - - name: Setup .NET 8.0 # net8 is always required. - uses: actions/setup-dotnet@v1 + dotnet-quality: 'ga' # Prefer a GA release, but use the RC if not available + - name: Setup .NET 8 (required) + uses: actions/setup-dotnet@v3 if: ${{ matrix.install-version != '8.0.x' }} with: - dotnet-version: 8.0.x + dotnet-version: '8.0.x' + dotnet-quality: 'ga' + - name: Setup .NET 9 (required) + uses: actions/setup-dotnet@v3 + if: ${{ matrix.install-version != '9.0.x' }} + with: + dotnet-version: '9.0.x' + dotnet-quality: 'ga' - name: Build # disable deterministic builds, just for test run. Deterministic builds break coverage for some reason run: dotnet build --configuration release /p:GITHUB_ACTIONS=false diff --git a/.github/workflows/sdk_build.yml b/.github/workflows/sdk_build.yml index 5e6fd3532..b6e263530 100644 --- a/.github/workflows/sdk_build.yml +++ b/.github/workflows/sdk_build.yml @@ -24,9 +24,10 @@ jobs: - name: Parse release version run: python ./.github/scripts/get_release_version.py - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x + dotnet-quality: 'ga' - name: Build run: dotnet build --configuration release - name: Generate Packages @@ -43,39 +44,49 @@ jobs: strategy: fail-fast: false matrix: - dotnet-version: ['6.0', '7.0', '8.0'] + dotnet-version: ['6.0', '7.0', '8.0', '9.0'] include: - dotnet-version: '6.0' - install-3: false display-name: '.NET 6.0' framework: 'net6' prefix: 'net6' install-version: '6.0.x' - dotnet-version: '7.0' - install-3: false display-name: '.NET 7.0' framework: 'net7' prefix: 'net7' install-version: '7.0.x' - dotnet-version: '8.0' - install-3: false display-name: '.NET 8.0' framework: 'net8' prefix: 'net8' install-version: '8.0.x' + - dotnet-version: '9.0' + display-name: '.NET 9.0' + framework: 'net9' + prefix: 'net9' + install-version: '9.0.x' steps: - uses: actions/checkout@v1 - name: Parse release version run: python ./.github/scripts/get_release_version.py - name: Setup ${{ matrix.display-name }} - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: ${{ matrix.install-version }} - - name: Setup .NET 8.0 # net8 is always required. - uses: actions/setup-dotnet@v1 + dotnet-quality: 'ga' # Prefer a GA release, but use the RC if not available + - name: Setup .NET 8 (required) + uses: actions/setup-dotnet@v3 if: ${{ matrix.install-version != '8.0.x' }} with: - dotnet-version: 8.0.x + dotnet-version: '8.0.x' + dotnet-quality: 'ga' + - name: Setup .NET 9 (required) + uses: actions/setup-dotnet@v3 + if: ${{ matrix.install-version != '9.0.x' }} + with: + dotnet-version: '9.0.x' + dotnet-quality: 'ga' - name: Build # disable deterministic builds, just for test run. Deterministic builds break coverage for some reason run: dotnet build --configuration release /p:GITHUB_ACTIONS=false diff --git a/daprdocs/content/en/dotnet-sdk-docs/_index.md b/daprdocs/content/en/dotnet-sdk-docs/_index.md index 60a4a1a61..75b16e40e 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/_index.md +++ b/daprdocs/content/en/dotnet-sdk-docs/_index.md @@ -18,7 +18,15 @@ Dapr offers a variety of packages to help with the development of .NET applicati - [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed - Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}) -- [.NET 6](https://dotnet.microsoft.com/download) or [.NET 8+](https://dotnet.microsoft.com/download) installed +- [.NET 6](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed + +{{% alert title="Note" color="primary" %}} + +Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages +and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 wil +continue to be supported by Dapr in v1.16 and later. + +{{% /alert %}} ## Installation diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md index eaa13625d..62e166da6 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md @@ -45,7 +45,15 @@ This project contains the implementation of the actor client which calls MyActor - [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed. - Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}). -- [.NET 6+](https://dotnet.microsoft.com/download) installed. Dapr .NET SDK uses [ASP.NET Core](https://docs.microsoft.com/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-6.0). +- [.NET 6](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed + +{{% alert title="Note" color="primary" %}} + +Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages +and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 wil +continue to be supported by Dapr in v1.16 and later. + +{{% /alert %}} ## Step 0: Prepare diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md index c8bc66175..5125b2cd7 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md @@ -16,10 +16,17 @@ In the .NET example project: - The main [`Program.cs`](https://github.com/dapr/dotnet-sdk/tree/master/examples/Jobs/JobsSample/Program.cs) file comprises the entirety of this demonstration. ## Prerequisites -- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost) -- [Dapr Jobs .NET SDK](https://github.com/dapr/dotnet-sdk) +- [.NET 6](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed + +{{% alert title="Note" color="primary" %}} + +Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages +and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 wil +continue to be supported by Dapr in v1.16 and later. + +{{% /alert %}} ## Set up the environment Clone the [.NET SDK repo](https://github.com/dapr/dotnet-sdk). diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md index f6d18bc58..9aa0c9f7a 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md @@ -18,11 +18,17 @@ In the .NET example project: ## Prerequisites -- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) -- [Dapr .NET SDK](https://github.com/dapr/dotnet-sdk/) +- [.NET 7](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed +{{% alert title="Note" color="primary" %}} + +Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages +and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 wil +continue to be supported by Dapr in v1.16 and later. + +{{% /alert %}} ## Set up the environment diff --git a/global.json b/global.json index fe53f92ae..3e41d7e1b 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "_comment": "This policy allows the 8.0.100 SDK or patches in that family.", "sdk": { - "version": "8.0.100", - "rollForward": "minor" + "version": "9.0.100-rc.2", + "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/src/Dapr.Common/Dapr.Common.csproj b/src/Dapr.Common/Dapr.Common.csproj index 31af3952c..d1e106b6d 100644 --- a/src/Dapr.Common/Dapr.Common.csproj +++ b/src/Dapr.Common/Dapr.Common.csproj @@ -1,7 +1,7 @@  - net6;net7;net8 + net6;net7;net8;net9 enable enable diff --git a/src/Dapr.Jobs/Dapr.Jobs.csproj b/src/Dapr.Jobs/Dapr.Jobs.csproj index 74c9bec23..9f209b0bd 100644 --- a/src/Dapr.Jobs/Dapr.Jobs.csproj +++ b/src/Dapr.Jobs/Dapr.Jobs.csproj @@ -1,7 +1,6 @@  - net6;net8 enable enable Dapr.Jobs diff --git a/src/Dapr.Jobs/Models/DaprJobSchedule.cs b/src/Dapr.Jobs/Models/DaprJobSchedule.cs index c1b592e12..8b1666727 100644 --- a/src/Dapr.Jobs/Models/DaprJobSchedule.cs +++ b/src/Dapr.Jobs/Models/DaprJobSchedule.cs @@ -67,7 +67,9 @@ public static DaprJobSchedule FromCronExpression(CronExpressionBuilder builder) /// public static DaprJobSchedule FromDateTime(DateTimeOffset scheduledTime) { +#if NET6_0 ArgumentNullException.ThrowIfNull(scheduledTime, nameof(scheduledTime)); +#endif return new DaprJobSchedule(scheduledTime.ToString("O")); } @@ -77,7 +79,9 @@ public static DaprJobSchedule FromDateTime(DateTimeOffset scheduledTime) /// The systemd Cron-like expression indicating when the job should be triggered. public static DaprJobSchedule FromExpression(string expression) { +#if NET6_0 ArgumentNullException.ThrowIfNull(expression, nameof(expression)); +#endif return new DaprJobSchedule(expression); } @@ -87,7 +91,9 @@ public static DaprJobSchedule FromExpression(string expression) /// The duration interval. public static DaprJobSchedule FromDuration(TimeSpan duration) { +#if NET6_0 ArgumentNullException.ThrowIfNull(duration, nameof(duration)); +#endif return new DaprJobSchedule(duration.ToDurationString()); } diff --git a/src/Dapr.Protos/Dapr.Protos.csproj b/src/Dapr.Protos/Dapr.Protos.csproj index 8a8804b22..5331f229c 100644 --- a/src/Dapr.Protos/Dapr.Protos.csproj +++ b/src/Dapr.Protos/Dapr.Protos.csproj @@ -1,6 +1,7 @@  + net6;net7;net8;net9 enable enable This package contains the reference protos used by develop services using Dapr. diff --git a/src/Dapr.Workflow/Dapr.Workflow.csproj b/src/Dapr.Workflow/Dapr.Workflow.csproj index 360d121ef..f24d41e40 100644 --- a/src/Dapr.Workflow/Dapr.Workflow.csproj +++ b/src/Dapr.Workflow/Dapr.Workflow.csproj @@ -3,6 +3,7 @@ + net6;net7;net8;net9 enable Dapr.Workflow Dapr Workflow Authoring SDK diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 35f0fbf7c..a74833a37 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ - net6;net8 + net6;net8;net9 $(RepoRoot)bin\$(Configuration)\prod\$(MSBuildProjectName)\ $(OutputPath)$(MSBuildProjectName).xml diff --git a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs index c64fd3427..ef1d7b9a0 100644 --- a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs +++ b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs @@ -34,6 +34,8 @@ public Test() 7; #elif NET8_0 8; +#elif NET9_0 + 9; #endif // diff --git a/test/Dapr.AspNetCore.IntegrationTest/CloudEventsIntegrationTest.cs b/test/Dapr.AspNetCore.IntegrationTest/CloudEventsIntegrationTest.cs index dd940a75d..9b0b5d3a3 100644 --- a/test/Dapr.AspNetCore.IntegrationTest/CloudEventsIntegrationTest.cs +++ b/test/Dapr.AspNetCore.IntegrationTest/CloudEventsIntegrationTest.cs @@ -146,7 +146,7 @@ public async Task CanSendBinaryCloudEvent_WithContentType() using (var factory = new AppWebApplicationFactory()) { var httpClient = factory.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { HandleCookies = false }); - + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/register-user") { Content = new StringContent( @@ -158,10 +158,10 @@ public async Task CanSendBinaryCloudEvent_WithContentType() Encoding.UTF8) }; request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - + var response = await httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); - + var userInfo = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), this.options); userInfo.Name.Should().Be("jimmy"); } diff --git a/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj b/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj index d51dc70e8..d7dd6d52a 100644 --- a/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj +++ b/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj @@ -7,14 +7,36 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + - + + + + + + + + + + + + diff --git a/test/Dapr.AspNetCore.Test/CloudEventsMiddlewareTest.cs b/test/Dapr.AspNetCore.Test/CloudEventsMiddlewareTest.cs index 9c1f1e005..2f9fab936 100644 --- a/test/Dapr.AspNetCore.Test/CloudEventsMiddlewareTest.cs +++ b/test/Dapr.AspNetCore.Test/CloudEventsMiddlewareTest.cs @@ -11,6 +11,8 @@ // limitations under the License. // ------------------------------------------------------------------------ +using Microsoft.Extensions.DependencyInjection; + namespace Dapr.AspNetCore.Test { using System.IO; @@ -33,7 +35,10 @@ public class CloudEventsMiddlewareTest [InlineData("application/cloudevents-batch+json")] // we don't support batch public async Task InvokeAsync_IgnoresOtherContentTypes(string contentType) { - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(); // Do verification in the scope of the middleware @@ -46,9 +51,10 @@ public async Task InvokeAsync_IgnoresOtherContentTypes(string contentType) var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = contentType; - context.Request.Body = MakeBody("Hello, world!"); + var context = new DefaultHttpContext + { + Request = { ContentType = contentType, Body = MakeBody("Hello, world!") } + }; await pipeline.Invoke(context); } @@ -62,7 +68,10 @@ public async Task InvokeAsync_IgnoresOtherContentTypes(string contentType) public async Task InvokeAsync_ReplacesBodyJson(string dataContentType, string charSet) { var encoding = charSet == null ? null : Encoding.GetEncoding(charSet); - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(); // Do verification in the scope of the middleware @@ -75,11 +84,17 @@ public async Task InvokeAsync_ReplacesBodyJson(string dataContentType, string ch var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = charSet == null ? "application/cloudevents+json" : $"application/cloudevents+json;charset={charSet}"; - context.Request.Body = dataContentType == null ? - MakeBody("{ \"data\": { \"name\":\"jimmy\" } }", encoding) : - MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding); + var context = new DefaultHttpContext { Request = + { + ContentType = + charSet == null + ? "application/cloudevents+json" + : $"application/cloudevents+json;charset={charSet}", + Body = dataContentType == null ? + MakeBody("{ \"data\": { \"name\":\"jimmy\" } }", encoding) : + MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding) + } + }; await pipeline.Invoke(context); } @@ -93,7 +108,10 @@ public async Task InvokeAsync_ReplacesBodyJson(string dataContentType, string ch public async Task InvokeAsync_ReplacesPascalCasedBodyJson(string dataContentType, string charSet) { var encoding = charSet == null ? null : Encoding.GetEncoding(charSet); - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(); // Do verification in the scope of the middleware @@ -106,11 +124,17 @@ public async Task InvokeAsync_ReplacesPascalCasedBodyJson(string dataContentType var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = charSet == null ? "application/cloudevents+json" : $"application/cloudevents+json;charset={charSet}"; - context.Request.Body = dataContentType == null ? - MakeBody("{ \"Data\": { \"name\":\"jimmy\" } }", encoding) : - MakeBody($"{{ \"DataContentType\": \"{dataContentType}\", \"Data\": {{ \"name\":\"jimmy\" }} }}", encoding); + var context = new DefaultHttpContext { Request = + { + ContentType = + charSet == null + ? "application/cloudevents+json" + : $"application/cloudevents+json;charset={charSet}", + Body = dataContentType == null ? + MakeBody("{ \"Data\": { \"name\":\"jimmy\" } }", encoding) : + MakeBody($"{{ \"DataContentType\": \"{dataContentType}\", \"Data\": {{ \"name\":\"jimmy\" }} }}", encoding) + } + }; await pipeline.Invoke(context); } @@ -124,7 +148,10 @@ public async Task InvokeAsync_ReplacesPascalCasedBodyJson(string dataContentType public async Task InvokeAsync_ForwardsJsonPropertiesAsHeaders(string dataContentType, string charSet) { var encoding = charSet == null ? null : Encoding.GetEncoding(charSet); - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(new CloudEventsMiddlewareOptions { ForwardCloudEventPropertiesAsHeaders = true @@ -143,11 +170,17 @@ public async Task InvokeAsync_ForwardsJsonPropertiesAsHeaders(string dataContent var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = charSet == null ? "application/cloudevents+json" : $"application/cloudevents+json;charset={charSet}"; - context.Request.Body = dataContentType == null ? - MakeBody("{ \"type\": \"Test.Type\", \"subject\": \"Test.Subject\", \"data\": { \"name\":\"jimmy\" } }", encoding) : - MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"type\":\"Test.Type\", \"subject\": \"Test.Subject\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding); + var context = new DefaultHttpContext { Request = + { + ContentType = + charSet == null + ? "application/cloudevents+json" + : $"application/cloudevents+json;charset={charSet}", + Body = dataContentType == null ? + MakeBody("{ \"type\": \"Test.Type\", \"subject\": \"Test.Subject\", \"data\": { \"name\":\"jimmy\" } }", encoding) : + MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"type\":\"Test.Type\", \"subject\": \"Test.Subject\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding) + } + }; await pipeline.Invoke(context); } @@ -161,7 +194,10 @@ public async Task InvokeAsync_ForwardsJsonPropertiesAsHeaders(string dataContent public async Task InvokeAsync_ForwardsIncludedJsonPropertiesAsHeaders(string dataContentType, string charSet) { var encoding = charSet == null ? null : Encoding.GetEncoding(charSet); - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(new CloudEventsMiddlewareOptions { ForwardCloudEventPropertiesAsHeaders = true, @@ -181,11 +217,17 @@ public async Task InvokeAsync_ForwardsIncludedJsonPropertiesAsHeaders(string dat var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = charSet == null ? "application/cloudevents+json" : $"application/cloudevents+json;charset={charSet}"; - context.Request.Body = dataContentType == null ? - MakeBody("{ \"type\": \"Test.Type\", \"subject\": \"Test.Subject\", \"data\": { \"name\":\"jimmy\" } }", encoding) : - MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"type\":\"Test.Type\", \"subject\": \"Test.Subject\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding); + var context = new DefaultHttpContext { Request = + { + ContentType = + charSet == null + ? "application/cloudevents+json" + : $"application/cloudevents+json;charset={charSet}", + Body = dataContentType == null ? + MakeBody("{ \"type\": \"Test.Type\", \"subject\": \"Test.Subject\", \"data\": { \"name\":\"jimmy\" } }", encoding) : + MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"type\":\"Test.Type\", \"subject\": \"Test.Subject\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding) + } + }; await pipeline.Invoke(context); } @@ -199,7 +241,10 @@ public async Task InvokeAsync_ForwardsIncludedJsonPropertiesAsHeaders(string dat public async Task InvokeAsync_DoesNotForwardExcludedJsonPropertiesAsHeaders(string dataContentType, string charSet) { var encoding = charSet == null ? null : Encoding.GetEncoding(charSet); - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(new CloudEventsMiddlewareOptions { ForwardCloudEventPropertiesAsHeaders = true, @@ -219,11 +264,17 @@ public async Task InvokeAsync_DoesNotForwardExcludedJsonPropertiesAsHeaders(stri var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = charSet == null ? "application/cloudevents+json" : $"application/cloudevents+json;charset={charSet}"; - context.Request.Body = dataContentType == null ? - MakeBody("{ \"type\": \"Test.Type\", \"subject\": \"Test.Subject\", \"data\": { \"name\":\"jimmy\" } }", encoding) : - MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"type\":\"Test.Type\", \"subject\": \"Test.Subject\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding); + var context = new DefaultHttpContext { Request = + { + ContentType = + charSet == null + ? "application/cloudevents+json" + : $"application/cloudevents+json;charset={charSet}", + Body = dataContentType == null ? + MakeBody("{ \"type\": \"Test.Type\", \"subject\": \"Test.Subject\", \"data\": { \"name\":\"jimmy\" } }", encoding) : + MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"type\":\"Test.Type\", \"subject\": \"Test.Subject\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding) + } + }; await pipeline.Invoke(context); } @@ -234,10 +285,13 @@ public async Task InvokeAsync_ReplacesBodyNonJsonData() // Our logic is based on the content-type, not the content. // Since this is for text-plain content, we're going to encode it as a JSON string // and store it in the data attribute - the middleware should JSON-decode it. - var input = "{ \"message\": \"hello, world\"}"; + const string input = "{ \"message\": \"hello, world\"}"; var expected = input; - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(); // Do verification in the scope of the middleware @@ -251,9 +305,12 @@ public async Task InvokeAsync_ReplacesBodyNonJsonData() var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = "application/cloudevents+json"; - context.Request.Body = MakeBody($"{{ \"datacontenttype\": \"text/plain\", \"data\": {JsonSerializer.Serialize(input)} }}"); + var context = new DefaultHttpContext { Request = + { + ContentType = "application/cloudevents+json", + Body = MakeBody($"{{ \"datacontenttype\": \"text/plain\", \"data\": {JsonSerializer.Serialize(input)} }}") + } + }; await pipeline.Invoke(context); } @@ -262,10 +319,13 @@ public async Task InvokeAsync_ReplacesBodyNonJsonData() public async Task InvokeAsync_ReplacesBodyNonJsonData_ExceptWhenSuppressed() { // Our logic is based on the content-type, not the content. This test tests the old bad behavior. - var input = "{ \"message\": \"hello, world\"}"; + const string input = "{ \"message\": \"hello, world\"}"; var expected = JsonSerializer.Serialize(input); - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(new CloudEventsMiddlewareOptions() { SuppressJsonDecodingOfTextPayloads = true, }); // Do verification in the scope of the middleware @@ -279,9 +339,12 @@ public async Task InvokeAsync_ReplacesBodyNonJsonData_ExceptWhenSuppressed() var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = "application/cloudevents+json"; - context.Request.Body = MakeBody($"{{ \"datacontenttype\": \"text/plain\", \"data\": {JsonSerializer.Serialize(input)} }}"); + var context = new DefaultHttpContext { Request = + { + ContentType = "application/cloudevents+json", + Body = MakeBody($"{{ \"datacontenttype\": \"text/plain\", \"data\": {JsonSerializer.Serialize(input)} }}") + } + }; await pipeline.Invoke(context); } @@ -291,10 +354,13 @@ public async Task InvokeAsync_ReplacesBodyNonJsonData_ExceptWhenSuppressed() [Fact] public async Task InvokeAsync_ReplacesBodyJson_NormalizesPayloadCharset() { - var dataContentType = "application/person+json;charset=UTF-16"; - var charSet = "UTF-16"; + const string dataContentType = "application/person+json;charset=UTF-16"; + const string charSet = "UTF-16"; var encoding = Encoding.GetEncoding(charSet); - var app = new ApplicationBuilder(null); + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(); // Do verification in the scope of the middleware @@ -307,10 +373,11 @@ public async Task InvokeAsync_ReplacesBodyJson_NormalizesPayloadCharset() var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = $"application/cloudevents+json;charset={charSet}"; - context.Request.Body = - MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding); + var context = new DefaultHttpContext { Request = + { + ContentType = $"application/cloudevents+json;charset={charSet}", Body = MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"data\": {{ \"name\":\"jimmy\" }} }}", encoding) + } + }; await pipeline.Invoke(context); } @@ -318,8 +385,11 @@ public async Task InvokeAsync_ReplacesBodyJson_NormalizesPayloadCharset() [Fact] public async Task InvokeAsync_ReadsBinaryData() { - var dataContentType = "application/octet-stream"; - var app = new ApplicationBuilder(null); + const string dataContentType = "application/octet-stream"; + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(); var data = new byte[] { 1, 2, 3 }; @@ -328,15 +398,18 @@ public async Task InvokeAsync_ReadsBinaryData() { httpContext.Request.ContentType.Should().Be(dataContentType); var bytes = new byte[httpContext.Request.Body.Length]; +#if NET9_0 + httpContext.Request.Body.ReadExactly(bytes, 0, bytes.Length); +#else httpContext.Request.Body.Read(bytes, 0, bytes.Length); +#endif bytes.Should().Equal(data); return Task.CompletedTask; }); var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = "application/cloudevents+json"; + var context = new DefaultHttpContext { Request = { ContentType = "application/cloudevents+json" } }; var base64Str = System.Convert.ToBase64String(data); context.Request.Body = @@ -348,10 +421,13 @@ public async Task InvokeAsync_ReadsBinaryData() [Fact] public async Task InvokeAsync_DataAndData64Set_ReturnsBadRequest() { - var dataContentType = "application/octet-stream"; - var app = new ApplicationBuilder(null); + const string dataContentType = "application/octet-stream"; + var serviceCollection = new ServiceCollection(); + var provider = serviceCollection.BuildServiceProvider(); + + var app = new ApplicationBuilder(provider); app.UseCloudEvents(); - var data = "{\"id\": \"1\"}"; + const string data = "{\"id\": \"1\"}"; // Do verification in the scope of the middleware app.Run(httpContext => @@ -364,8 +440,7 @@ public async Task InvokeAsync_DataAndData64Set_ReturnsBadRequest() var pipeline = app.Build(); - var context = new DefaultHttpContext(); - context.Request.ContentType = "application/cloudevents+json"; + var context = new DefaultHttpContext { Request = { ContentType = "application/cloudevents+json" } }; var bytes = Encoding.UTF8.GetBytes(data); var base64Str = System.Convert.ToBase64String(bytes); context.Request.Body = @@ -391,7 +466,11 @@ private static string ReadBody(Stream stream, Encoding encoding = null) encoding ??= Encoding.UTF8; var bytes = new byte[stream.Length]; +#if NET9_0 + stream.ReadExactly(bytes, 0, bytes.Length); +#else stream.Read(bytes, 0, bytes.Length); +#endif var str = encoding.GetString(bytes); return str; } diff --git a/test/Dapr.Client.Test/DaprClientTest.cs b/test/Dapr.Client.Test/DaprClientTest.cs index 01d22edcf..e280728c2 100644 --- a/test/Dapr.Client.Test/DaprClientTest.cs +++ b/test/Dapr.Client.Test/DaprClientTest.cs @@ -45,7 +45,7 @@ public void CreateInvokeHttpClient_WithoutAppId() var client = DaprClient.CreateInvokeHttpClient(daprEndpoint: "http://localhost:3500"); Assert.Null(client.BaseAddress); } - + [Fact] public void CreateInvokeHttpClient_InvalidDaprEndpoint_InvalidFormat() { diff --git a/test/Dapr.E2E.Test/DaprTestApp.cs b/test/Dapr.E2E.Test/DaprTestApp.cs index 152aeee98..2330785d8 100644 --- a/test/Dapr.E2E.Test/DaprTestApp.cs +++ b/test/Dapr.E2E.Test/DaprTestApp.cs @@ -139,6 +139,7 @@ private static string GetTargetFrameworkName() ".NETCoreApp,Version=v6.0" => "net6", ".NETCoreApp,Version=v7.0" => "net7", ".NETCoreApp,Version=v8.0" => "net8", + ".NETCoreApp,Version=v9.0" => "net9", _ => throw new InvalidOperationException($"Unsupported target framework: {targetFrameworkName}") }; } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 50b029a12..e3a49b72f 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -2,7 +2,7 @@ - net6;net7;net8 + net6;net7;net8;net9 $(RepoRoot)bin\$(Configuration)\test\$(MSBuildProjectName)\