Skip to content

Commit c7bb7ca

Browse files
authored
Merge pull request #903 from microsoftgraph/andrueastman/fixTrimmingWarnings
fix: resolve trimming warnings in project
2 parents 958a753 + 2545cae commit c7bb7ca

File tree

11 files changed

+274
-46
lines changed

11 files changed

+274
-46
lines changed

.github/policies/msgraph-sdk-dotnet-core-branch-protection.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ configuration:
3636
# 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
3737
requiredStatusChecks:
3838
- Build and Test # Contains CodeQL
39+
- Validate Project for Trimming
3940
- license/cla
4041
# Require branches to be up to date before merging. boolean
4142
requiresStrictStatusChecks: true

.github/workflows/sonarcloud.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
CoverletOutputFormat: "opencover" # https://github.com/microsoft/vstest/issues/4014#issuecomment-1307913682
7171
shell: pwsh
7272
run: |
73-
./.sonar/scanner/dotnet-sonarscanner begin /k:"microsoftgraph_msgraph-sdk-dotnet-core" /o:"microsoftgraph2" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="tests/Microsoft.Graph.DotnetCore.Core.Test/coverage.opencover.xml"
73+
./.sonar/scanner/dotnet-sonarscanner begin /k:"microsoftgraph_msgraph-sdk-dotnet-core" /o:"microsoftgraph2" /d:sonar.scanner.scanAll=false /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="tests/Microsoft.Graph.DotnetCore.Core.Test/coverage.opencover.xml"
7474
dotnet workload restore
7575
dotnet build
7676
dotnet test Microsoft.Graph.Core.sln --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --framework net6.0

.github/workflows/validatePullRequest.yml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ jobs:
4040

4141
- name: Install needed dotnet workloads
4242
run: dotnet workload install android macos ios maccatalyst
43+
44+
- name: Restore nuget dependencies
45+
run: dotnet restore ${{ env.solutionName }}
4346

4447
- name: Lint the code
4548
run: dotnet format --verify-no-changes
46-
47-
- name: Restore nuget dependencies
48-
run: dotnet restore ${{ env.solutionName }}
4949

5050
- name: Build
5151
run: dotnet build ${{ env.solutionName }} -c Debug /p:UseSharedCompilation=false,IncludeMauiTargets=true
@@ -56,4 +56,17 @@ jobs:
5656
- name: Perform CodeQL Analysis
5757
uses: github/codeql-action/analyze@v3
5858

59+
validate-trimming:
60+
name: Validate Project for Trimming
61+
runs-on: windows-latest
62+
steps:
63+
- uses: actions/checkout@v4.1.7
64+
65+
- name: Setup .NET
66+
uses: actions/setup-dotnet@v4
67+
with:
68+
dotnet-version: 8.x
5969

70+
- name: Validate Trimming warnings
71+
run: dotnet publish -c Release -r win-x64 /p:TreatWarningsAsErrors=true /warnaserror -f net8.0
72+
working-directory: ./tests/Microsoft.Graph.DotnetCore.Core.Trimming

src/Microsoft.Graph.Core/Microsoft.Graph.Core.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,16 @@
6262
</None>
6363
</ItemGroup>
6464
<ItemGroup>
65-
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.0.1" />
66-
<PackageReference Include="Microsoft.IdentityModel.Validators" Version="8.0.1" />
65+
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.0.2" />
66+
<PackageReference Include="Microsoft.IdentityModel.Validators" Version="8.0.2" />
6767
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
6868
<PackageReference Include="Microsoft.Kiota.Abstractions" Version="1.12.3" />
6969
<PackageReference Include="Microsoft.Kiota.Authentication.Azure" Version="1.12.3" />
7070
<PackageReference Include="Microsoft.Kiota.Serialization.Json" Version="1.12.3" />
71-
<PackageReference Include="Microsoft.Kiota.Serialization.Text" Version="1.11.3" />
72-
<PackageReference Include="Microsoft.Kiota.Serialization.Form" Version="1.11.3" />
71+
<PackageReference Include="Microsoft.Kiota.Serialization.Text" Version="1.12.3" />
72+
<PackageReference Include="Microsoft.Kiota.Serialization.Form" Version="1.12.3" />
7373
<PackageReference Include="Microsoft.Kiota.Http.HttpClientLibrary" Version="1.12.3" />
74-
<PackageReference Include="Microsoft.Kiota.Serialization.Multipart" Version="1.11.3" />
74+
<PackageReference Include="Microsoft.Kiota.Serialization.Multipart" Version="1.12.3" />
7575
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.11.20">
7676
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
7777
<PrivateAssets>all</PrivateAssets>

src/Microsoft.Graph.Core/Tasks/LargeFileUploadTask.cs

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ namespace Microsoft.Graph
66
{
77
using System;
88
using System.Collections.Generic;
9+
using System.ComponentModel;
910
using System.IO;
1011
using System.Net.Http;
12+
using System.Text.Json;
1113
using System.Threading;
1214
using System.Threading.Tasks;
1315
using Microsoft.Graph.Core.Models;
1416
using Microsoft.Kiota.Abstractions;
1517
using Microsoft.Kiota.Abstractions.Authentication;
1618
using Microsoft.Kiota.Abstractions.Serialization;
19+
using Microsoft.Kiota.Serialization.Json;
1720

1821
/// <summary>
1922
/// Task to help with resumable large file uploads.
@@ -40,7 +43,23 @@ private IUploadSession Session
4043
/// <param name="maxSliceSize">Max size(in bytes) of each slice to be uploaded. Defaults to 5MB. When uploading to OneDrive or SharePoint, this value needs to be a multiple of 320 KiB (327,680 bytes).
4144
/// If less than 0, default value of 5 MiB is used.</param>
4245
/// <param name="requestAdapter"><see cref="IRequestAdapter"/> to use for making upload requests. The client should not set Auth headers as upload urls do not need them.</param>
43-
public LargeFileUploadTask(IParsable uploadSession, Stream uploadStream, int maxSliceSize = -1, IRequestAdapter requestAdapter = null)
46+
[Obsolete("Use the overload that takes in IUploadSession instead.")]
47+
[EditorBrowsable(EditorBrowsableState.Never)]
48+
public LargeFileUploadTask(IParsable uploadSession, Stream uploadStream, int maxSliceSize = -1,
49+
IRequestAdapter requestAdapter = null) : this(ExtractSessionFromParsable(uploadSession), uploadStream, maxSliceSize, requestAdapter)
50+
{
51+
}
52+
53+
/// <summary>
54+
/// Task to help with resumable large file uploads. Generates slices based on <paramref name="uploadSession"/>
55+
/// information, and can control uploading of requests.
56+
/// </summary>
57+
/// <param name="uploadSession">Session information of type <see cref="IUploadSession"/>></param>
58+
/// <param name="uploadStream">Readable, seekable stream to be uploaded. Length of session is determined via uploadStream.Length</param>
59+
/// <param name="maxSliceSize">Max size(in bytes) of each slice to be uploaded. Defaults to 5MB. When uploading to OneDrive or SharePoint, this value needs to be a multiple of 320 KiB (327,680 bytes).
60+
/// If less than 0, default value of 5 MiB is used.</param>
61+
/// <param name="requestAdapter"><see cref="IRequestAdapter"/> to use for making upload requests. The client should not set Auth headers as upload urls do not need them.</param>
62+
public LargeFileUploadTask(IUploadSession uploadSession, Stream uploadStream, int maxSliceSize = -1, IRequestAdapter requestAdapter = null)
4463
{
4564
if (!uploadStream.CanRead || !uploadStream.CanSeek)
4665
{
@@ -50,8 +69,8 @@ public LargeFileUploadTask(IParsable uploadSession, Stream uploadStream, int max
5069
{
5170
throw new ArgumentException("Must a stream that is not empty");
5271
}
53-
this.Session = ExtractSessionFromParsable(uploadSession);
54-
this._requestAdapter = requestAdapter ?? this.InitializeAdapter(Session.UploadUrl);
72+
this.Session = uploadSession;
73+
this._requestAdapter = requestAdapter ?? InitializeAdapter(Session.UploadUrl);
5574
this._uploadStream = uploadStream;
5675
this._rangesRemaining = this.GetRangesRemaining(Session);
5776
this._maxSliceSize = maxSliceSize < 0 ? DefaultMaxSliceSize : maxSliceSize;
@@ -63,31 +82,34 @@ public LargeFileUploadTask(IParsable uploadSession, Stream uploadStream, int max
6382
/// <param name="uploadSession"><see cref="IParsable"/> to initialize an <see cref="IUploadSession"/> from</param>
6483
/// <returns>A <see cref="IUploadSession"/> instance</returns>
6584
/// <exception cref="NotImplementedException"></exception>
66-
private IUploadSession ExtractSessionFromParsable(IParsable uploadSession)
85+
internal static IUploadSession ExtractSessionFromParsable(IParsable uploadSession)
6786
{
68-
if (!uploadSession.GetFieldDeserializers().ContainsKey("expirationDateTime"))
87+
if (uploadSession is IUploadSession uploadSessionCast)
88+
{
89+
return uploadSessionCast;
90+
}
91+
92+
// this scenario (unlikely) will occur in the event the model in the service libraries hasn't been updated to implement the IUploadSession interface from core.
93+
var fieldDeserializers = uploadSession.GetFieldDeserializers();
94+
if (!fieldDeserializers.ContainsKey("expirationDateTime"))
6995
throw new ArgumentException("The Parsable does not contain the 'expirationDateTime' property");
70-
if (!uploadSession.GetFieldDeserializers().ContainsKey("nextExpectedRanges"))
96+
if (!fieldDeserializers.ContainsKey("nextExpectedRanges"))
7197
throw new ArgumentException("The Parsable does not contain the 'nextExpectedRanges' property");
72-
if (!uploadSession.GetFieldDeserializers().ContainsKey("uploadUrl"))
98+
if (!fieldDeserializers.ContainsKey("uploadUrl"))
7399
throw new ArgumentException("The Parsable does not contain the 'uploadUrl' property");
74100

75-
var uploadSessionType = uploadSession.GetType();
76-
77-
return new UploadSession()
78-
{
79-
ExpirationDateTime = uploadSessionType.GetProperty("ExpirationDateTime").GetValue(uploadSession, null) as DateTimeOffset?,
80-
NextExpectedRanges = uploadSessionType.GetProperty("NextExpectedRanges").GetValue(uploadSession, null) as List<string>,
81-
UploadUrl = uploadSessionType.GetProperty("UploadUrl").GetValue(uploadSession, null) as string
82-
};
101+
// convert to local type as we don't have the type info for the upload session just that it implements IParsable
102+
using var uploadSessionStream = KiotaJsonSerializer.SerializeAsStream(uploadSession, false);// just in case there's a backing store
103+
var uploadSessionJsonNode = new JsonParseNode(JsonDocument.Parse(uploadSessionStream).RootElement);
104+
return uploadSessionJsonNode.GetObjectValue(UploadSession.CreateFromDiscriminatorValue);
83105
}
84106

85107
/// <summary>
86108
/// Initialize a baseClient to use for the upload that does not have Auth enabled as the upload URLs explicitly do not need authentication.
87109
/// </summary>
88110
/// <param name="uploadUrl">Url to perform the upload to from the session</param>
89111
/// <returns></returns>
90-
private IRequestAdapter InitializeAdapter(string uploadUrl)
112+
private static BaseGraphRequestAdapter InitializeAdapter(string uploadUrl)
91113
{
92114
HttpClient httpClient = GraphClientFactory.Create(); //no auth
93115
httpClient.SetFeatureFlag(FeatureFlag.FileUploadTask);
@@ -196,7 +218,7 @@ public async Task<UploadResult<T>> UploadAsync(IProgress<long> progress = null,
196218
return uploadResult;
197219
}
198220

199-
ThrowIfUploadCancelled(cancellationToken, trackedExceptions);
221+
ThrowIfUploadCancelled(trackedExceptions, cancellationToken);
200222
}
201223

202224
await this.UpdateSessionStatusAsync(cancellationToken).ConfigureAwait(false);
@@ -207,7 +229,7 @@ public async Task<UploadResult<T>> UploadAsync(IProgress<long> progress = null,
207229
await Task.Delay(2000 * uploadTries * uploadTries, cancellationToken).ConfigureAwait(false);
208230
}
209231

210-
ThrowIfUploadCancelled(cancellationToken, trackedExceptions);
232+
ThrowIfUploadCancelled(trackedExceptions, cancellationToken);
211233
}
212234

213235
throw new TaskCanceledException("Upload failed too many times. See InnerException for list of exceptions that occured.", new AggregateException(trackedExceptions.ToArray()));
@@ -292,7 +314,7 @@ private long NextSliceSize(long rangeBegin, long rangeEnd)
292314
: sizeBasedOnRange;
293315
}
294316

295-
private void ThrowIfUploadCancelled(CancellationToken cancellationToken, ICollection<Exception> trackedExceptions)
317+
private static void ThrowIfUploadCancelled(ICollection<Exception> trackedExceptions, CancellationToken cancellationToken)
296318
{
297319
if (cancellationToken.IsCancellationRequested)
298320
throw new OperationCanceledException("File upload cancelled. See InnerException for list of exceptions that occured.", new AggregateException(trackedExceptions));

src/Microsoft.Graph.Core/Tasks/PageIterator.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ namespace Microsoft.Graph
1010
using System.Threading.Tasks;
1111
using Microsoft.Kiota.Abstractions;
1212
using Microsoft.Kiota.Abstractions.Serialization;
13+
#if NET5_0_OR_GREATER
14+
using System.Diagnostics.CodeAnalysis;
15+
#endif
1316

1417
/*
1518
Spec https://github.com/microsoftgraph/msgraph-sdk-design/blob/main/tasks/PageIteratorTask.md
@@ -21,7 +24,11 @@ namespace Microsoft.Graph
2124
/// </summary>
2225
/// <typeparam name="TEntity">The Microsoft Graph entity type returned in the result set.</typeparam>
2326
/// <typeparam name="TCollectionPage">The Microsoft Graph collection response type returned in the collection response.</typeparam>
27+
#if NET5_0_OR_GREATER
28+
public class PageIterator<TEntity, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TCollectionPage> where TCollectionPage : IParsable, IAdditionalDataHolder, new()
29+
#else
2430
public class PageIterator<TEntity, TCollectionPage> where TCollectionPage : IParsable, IAdditionalDataHolder, new()
31+
#endif
2532
{
2633
private IRequestAdapter _requestAdapter;
2734
private TCollectionPage _currentPage;
@@ -364,7 +371,7 @@ public async Task ResumeAsync(CancellationToken token)
364371
/// <exception cref="ArgumentException">Thrown when the object doesn't contain a collection inside it</exception>
365372
private static List<TEntity> ExtractEntityListFromParsable(TCollectionPage parsableCollection)
366373
{
367-
return parsableCollection.GetType().GetProperty("Value")?.GetValue(parsableCollection, null) as List<TEntity> ?? throw new ArgumentException("The Parsable does not contain a collection property");
374+
return typeof(TCollectionPage).GetProperty("Value")?.GetValue(parsableCollection, null) as List<TEntity> ?? throw new ArgumentException("The Parsable does not contain a collection property");
368375
}
369376

370377
/// <summary>
@@ -375,7 +382,7 @@ private static List<TEntity> ExtractEntityListFromParsable(TCollectionPage parsa
375382
/// <returns></returns>
376383
private static string ExtractNextLinkFromParsable(TCollectionPage parsableCollection, string nextLinkPropertyName = "OdataNextLink")
377384
{
378-
var nextLinkProperty = parsableCollection.GetType().GetProperty(nextLinkPropertyName);
385+
var nextLinkProperty = typeof(TCollectionPage).GetProperty(nextLinkPropertyName);
379386
if (nextLinkProperty != null &&
380387
nextLinkProperty.GetValue(parsableCollection, null) is string nextLinkString
381388
&& !string.IsNullOrEmpty(nextLinkString))
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
namespace Microsoft.Graph.DotnetCore.Core.Test.Mocks
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Microsoft.Kiota.Abstractions.Serialization;
7+
8+
/// <summary>
9+
/// Concrete implementation of the IUploadSession interface
10+
/// </summary>
11+
internal class MockUploadSessionWithoutUploadSessionInterface : IParsable, IAdditionalDataHolder
12+
{
13+
/// <summary>
14+
/// Expiration date of the upload session
15+
/// </summary>
16+
public DateTimeOffset? ExpirationDateTime
17+
{
18+
get; set;
19+
}
20+
21+
/// <summary>
22+
/// The ranges yet to be uploaded to the server
23+
/// </summary>
24+
public List<string> NextExpectedRanges
25+
{
26+
get; set;
27+
}
28+
29+
/// <summary>
30+
/// The URL for upload
31+
/// </summary>
32+
public string UploadUrl
33+
{
34+
get; set;
35+
}
36+
37+
/// <summary>
38+
/// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
39+
/// </summary>
40+
public IDictionary<string, object> AdditionalData { get; set; } = new Dictionary<string, object>();
41+
42+
/// <summary>
43+
/// The deserialization information for the current model
44+
/// </summary>
45+
public IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
46+
{
47+
return new Dictionary<string, Action<IParseNode>>(StringComparer.OrdinalIgnoreCase)
48+
{
49+
{"expirationDateTime", (n) => { ExpirationDateTime = n.GetDateTimeOffsetValue(); } },
50+
{"nextExpectedRanges", (n) => { NextExpectedRanges = n.GetCollectionOfPrimitiveValues<string>().ToList(); } },
51+
{"uploadUrl", (n) => { UploadUrl = n.GetStringValue(); } },
52+
};
53+
}
54+
55+
/// <summary>
56+
/// Serializes information the current object
57+
/// <param name="writer">Serialization writer to use to serialize this model</param>
58+
/// </summary>
59+
public void Serialize(ISerializationWriter writer)
60+
{
61+
_ = writer ?? throw new ArgumentNullException(nameof(writer));
62+
writer.WriteDateTimeOffsetValue("expirationDateTime", ExpirationDateTime);
63+
writer.WriteCollectionOfPrimitiveValues<string>("nextExpectedRanges", NextExpectedRanges);
64+
writer.WriteStringValue("uploadUrl", UploadUrl);
65+
writer.WriteAdditionalData(AdditionalData);
66+
}
67+
68+
/// <summary>
69+
/// Creates a new instance of the appropriate class based on discriminator value
70+
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
71+
/// </summary>
72+
public static MockUploadSessionWithoutUploadSessionInterface CreateFromDiscriminatorValue(IParseNode parseNode)
73+
{
74+
_ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
75+
return new MockUploadSessionWithoutUploadSessionInterface();
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)