diff --git a/.github/policies/msgraph-sdk-dotnet-core-branch-protection.yml b/.github/policies/msgraph-sdk-dotnet-core-branch-protection.yml index f25d119ea..c7f0b13d4 100644 --- a/.github/policies/msgraph-sdk-dotnet-core-branch-protection.yml +++ b/.github/policies/msgraph-sdk-dotnet-core-branch-protection.yml @@ -36,6 +36,7 @@ configuration: # Required status checks to pass before merging. Values can be any string, but if the value does not correspond to any existing status check, the status check will be stuck on pending for status since nothing exists to push an actual status requiredStatusChecks: - Build and Test # Contains CodeQL + - Validate Project for Trimming - license/cla # Require branches to be up to date before merging. boolean requiresStrictStatusChecks: true diff --git a/.github/workflows/validatePullRequest.yml b/.github/workflows/validatePullRequest.yml index cf139692d..7311379a3 100644 --- a/.github/workflows/validatePullRequest.yml +++ b/.github/workflows/validatePullRequest.yml @@ -40,12 +40,12 @@ jobs: - name: Install needed dotnet workloads run: dotnet workload install android macos ios maccatalyst + + - name: Restore nuget dependencies + run: dotnet restore ${{ env.solutionName }} - name: Lint the code run: dotnet format --verify-no-changes - - - name: Restore nuget dependencies - run: dotnet restore ${{ env.solutionName }} - name: Build run: dotnet build ${{ env.solutionName }} -c Debug /p:UseSharedCompilation=false,IncludeMauiTargets=true @@ -56,4 +56,17 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 + validate-trimming: + name: Validate Project for Trimming + runs-on: windows-latest + steps: + - uses: actions/checkout@v4.1.7 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Validate Trimming warnings + run: dotnet publish -c Release -r win-x64 /p:TreatWarningsAsErrors=true /warnaserror -f net8.0 + working-directory: ./tests/Microsoft.Graph.DotnetCore.Core.Trimming \ No newline at end of file diff --git a/src/Microsoft.Graph.Core/Microsoft.Graph.Core.csproj b/src/Microsoft.Graph.Core/Microsoft.Graph.Core.csproj index 55dbbcf7f..d03f7d6de 100644 --- a/src/Microsoft.Graph.Core/Microsoft.Graph.Core.csproj +++ b/src/Microsoft.Graph.Core/Microsoft.Graph.Core.csproj @@ -62,16 +62,16 @@ - - + + - - + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Microsoft.Graph.Core/Tasks/LargeFileUploadTask.cs b/src/Microsoft.Graph.Core/Tasks/LargeFileUploadTask.cs index e51778afc..d6b66bd4f 100644 --- a/src/Microsoft.Graph.Core/Tasks/LargeFileUploadTask.cs +++ b/src/Microsoft.Graph.Core/Tasks/LargeFileUploadTask.cs @@ -8,12 +8,14 @@ namespace Microsoft.Graph using System.Collections.Generic; using System.IO; using System.Net.Http; + using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Graph.Core.Models; using Microsoft.Kiota.Abstractions; using Microsoft.Kiota.Abstractions.Authentication; using Microsoft.Kiota.Abstractions.Serialization; + using Microsoft.Kiota.Serialization.Json; /// /// Task to help with resumable large file uploads. @@ -63,7 +65,7 @@ public LargeFileUploadTask(IParsable uploadSession, Stream uploadStream, int max /// to initialize an from /// A instance /// - private IUploadSession ExtractSessionFromParsable(IParsable uploadSession) + internal static IUploadSession ExtractSessionFromParsable(IParsable uploadSession) { if (!uploadSession.GetFieldDeserializers().ContainsKey("expirationDateTime")) throw new ArgumentException("The Parsable does not contain the 'expirationDateTime' property"); @@ -72,14 +74,10 @@ private IUploadSession ExtractSessionFromParsable(IParsable uploadSession) if (!uploadSession.GetFieldDeserializers().ContainsKey("uploadUrl")) throw new ArgumentException("The Parsable does not contain the 'uploadUrl' property"); - var uploadSessionType = uploadSession.GetType(); - - return new UploadSession() - { - ExpirationDateTime = uploadSessionType.GetProperty("ExpirationDateTime").GetValue(uploadSession, null) as DateTimeOffset?, - NextExpectedRanges = uploadSessionType.GetProperty("NextExpectedRanges").GetValue(uploadSession, null) as List, - UploadUrl = uploadSessionType.GetProperty("UploadUrl").GetValue(uploadSession, null) as string - }; + // convert to local type as we don't have the type info for the upload session just that it implements IParsable + var uploadSessionStream = KiotaSerializer.SerializeAsStream(CoreConstants.MimeTypeNames.Application.Json, uploadSession, false);// just in case there's a backing store + var uploadSessionJsonNode = new JsonParseNode(JsonDocument.Parse(uploadSessionStream).RootElement); + return uploadSessionJsonNode.GetObjectValue(UploadSession.CreateFromDiscriminatorValue); } /// diff --git a/src/Microsoft.Graph.Core/Tasks/PageIterator.cs b/src/Microsoft.Graph.Core/Tasks/PageIterator.cs index 4859860ca..f13bb116b 100644 --- a/src/Microsoft.Graph.Core/Tasks/PageIterator.cs +++ b/src/Microsoft.Graph.Core/Tasks/PageIterator.cs @@ -10,6 +10,9 @@ namespace Microsoft.Graph using System.Threading.Tasks; using Microsoft.Kiota.Abstractions; using Microsoft.Kiota.Abstractions.Serialization; +#if NET5_0_OR_GREATER + using System.Diagnostics.CodeAnalysis; +#endif /* Spec https://github.com/microsoftgraph/msgraph-sdk-design/blob/main/tasks/PageIteratorTask.md @@ -21,7 +24,11 @@ namespace Microsoft.Graph /// /// The Microsoft Graph entity type returned in the result set. /// The Microsoft Graph collection response type returned in the collection response. +#if NET5_0_OR_GREATER + public class PageIterator where TCollectionPage : IParsable, IAdditionalDataHolder, new() +#else public class PageIterator where TCollectionPage : IParsable, IAdditionalDataHolder, new() +#endif { private IRequestAdapter _requestAdapter; private TCollectionPage _currentPage; @@ -364,7 +371,7 @@ public async Task ResumeAsync(CancellationToken token) /// Thrown when the object doesn't contain a collection inside it private static List ExtractEntityListFromParsable(TCollectionPage parsableCollection) { - return parsableCollection.GetType().GetProperty("Value")?.GetValue(parsableCollection, null) as List ?? throw new ArgumentException("The Parsable does not contain a collection property"); + return typeof(TCollectionPage).GetProperty("Value")?.GetValue(parsableCollection, null) as List ?? throw new ArgumentException("The Parsable does not contain a collection property"); } /// @@ -375,7 +382,7 @@ private static List ExtractEntityListFromParsable(TCollectionPage parsa /// private static string ExtractNextLinkFromParsable(TCollectionPage parsableCollection, string nextLinkPropertyName = "OdataNextLink") { - var nextLinkProperty = parsableCollection.GetType().GetProperty(nextLinkPropertyName); + var nextLinkProperty = typeof(TCollectionPage).GetProperty(nextLinkPropertyName); if (nextLinkProperty != null && nextLinkProperty.GetValue(parsableCollection, null) is string nextLinkString && !string.IsNullOrEmpty(nextLinkString)) diff --git a/tests/Microsoft.Graph.DotnetCore.Core.Test/Tasks/LargeFileUploadTaskTests.cs b/tests/Microsoft.Graph.DotnetCore.Core.Test/Tasks/LargeFileUploadTaskTests.cs index 0de4840d5..cd6ba0add 100644 --- a/tests/Microsoft.Graph.DotnetCore.Core.Test/Tasks/LargeFileUploadTaskTests.cs +++ b/tests/Microsoft.Graph.DotnetCore.Core.Test/Tasks/LargeFileUploadTaskTests.cs @@ -51,6 +51,24 @@ public void ThrowsOnEmptyStream() Assert.NotNull(exception); } [Fact] + public void ParsesUploadSessionWithoutReflection() + { + + var uploadSession = new UploadSession + { + NextExpectedRanges = new List() { "0-" }, + UploadUrl = "http://localhost", + ExpirationDateTime = DateTimeOffset.Parse("2019-11-07T06:39:31.499Z") + }; + + var parsedSession = LargeFileUploadTask.ExtractSessionFromParsable(uploadSession); + + Assert.Equal(uploadSession.UploadUrl, parsedSession.UploadUrl); + Assert.Equal(uploadSession.ExpirationDateTime, parsedSession.ExpirationDateTime); + Assert.Equal(uploadSession.NextExpectedRanges.Count, parsedSession.NextExpectedRanges.Count); + Assert.Equal(uploadSession.NextExpectedRanges[0], parsedSession.NextExpectedRanges[0]); + } + [Fact] public void AllowsVariableSliceSize() { byte[] mockData = new byte[1000000]; diff --git a/tests/Microsoft.Graph.DotnetCore.Core.Trimming/Microsoft.Graph.DotnetCore.Core.Trimming.csproj b/tests/Microsoft.Graph.DotnetCore.Core.Trimming/Microsoft.Graph.DotnetCore.Core.Trimming.csproj new file mode 100644 index 000000000..c35b98381 --- /dev/null +++ b/tests/Microsoft.Graph.DotnetCore.Core.Trimming/Microsoft.Graph.DotnetCore.Core.Trimming.csproj @@ -0,0 +1,17 @@ + + + Exe + net8.0 + enable + enable + true + true + false + true + true + + + + + + diff --git a/tests/Microsoft.Graph.DotnetCore.Core.Trimming/Program.cs b/tests/Microsoft.Graph.DotnetCore.Core.Trimming/Program.cs new file mode 100644 index 000000000..47cd26fb1 --- /dev/null +++ b/tests/Microsoft.Graph.DotnetCore.Core.Trimming/Program.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Graph.DotnetCore.Core.Trimming; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } +} diff --git a/tests/Microsoft.Graph.DotnetCore.Core.Trimming/global.json b/tests/Microsoft.Graph.DotnetCore.Core.Trimming/global.json new file mode 100644 index 000000000..d251ac4e7 --- /dev/null +++ b/tests/Microsoft.Graph.DotnetCore.Core.Trimming/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.101", /* https://github.com/dotnet/maui/wiki/.NET-7-and-.NET-MAUI */ + "rollForward": "major" + } +}