Skip to content

Commit 92ac9e2

Browse files
committed
Refactor and update project structure and methods
- Renamed `AsyncEnumerableGraphExtensions.cs` to `GraphExtensions.cs`, adding methods for paginated responses and batching requests. - Refactored `AuthService.cs` to use instance fields and methods, improved security in `GetBrowserCredential`, and using. - Refactored `MainViewModel.cs` to consolidate `IsBusyWrapper` methods and updated pagination handling.
1 parent d267685 commit 92ac9e2

File tree

8 files changed

+88
-104
lines changed

8 files changed

+88
-104
lines changed

MsGraphSamples.Services/AuthService.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,34 @@ public interface IAuthService
1616

1717
public class AuthService : IAuthService
1818
{
19-
private static readonly IConfiguration _configuration = new ConfigurationBuilder().AddUserSecrets<AuthService>().Build();
19+
private readonly IConfiguration _configuration = new ConfigurationBuilder().AddUserSecrets<AuthService>().Build();
2020

21-
private const string _tokenPath = "authToken.bin";
21+
private readonly string _tokenPath;
2222
private static readonly string[] _scopes = ["Directory.Read.All"];
2323

2424
private GraphServiceClient? _graphClient;
2525

2626
//public GraphServiceClient GraphClient => _graphClient ??= new GraphServiceClient(GetAppCredential());
2727
public GraphServiceClient GraphClient => _graphClient ??= new GraphServiceClient(GetBrowserCredential());
28-
29-
private static ClientSecretCredential GetAppCredential() => new(
28+
29+
public AuthService()
30+
{
31+
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
32+
var appName = AppDomain.CurrentDomain.FriendlyName;
33+
_tokenPath = Path.Combine(localAppData, appName, "authToken.bin");
34+
}
35+
36+
private ClientSecretCredential GetAppCredential() => new(
3037
_configuration["tenantId"],
3138
_configuration["clientId"],
3239
_configuration["clientSecret"]);
3340

34-
private static InteractiveBrowserCredential GetBrowserCredential()
41+
private InteractiveBrowserCredential GetBrowserCredential()
3542
{
3643
var credentialOptions = new InteractiveBrowserCredentialOptions
3744
{
3845
ClientId = _configuration["clientId"],
39-
TokenCachePersistenceOptions = new TokenCachePersistenceOptions() { UnsafeAllowUnencryptedStorage = true }
46+
TokenCachePersistenceOptions = new TokenCachePersistenceOptions()// { UnsafeAllowUnencryptedStorage = true }
4047
};
4148

4249
if (File.Exists(_tokenPath))
@@ -53,8 +60,11 @@ private static InteractiveBrowserCredential GetBrowserCredential()
5360
var browserCredential = new InteractiveBrowserCredential(credentialOptions);
5461
var tokenRequestContext = new TokenRequestContext(_scopes);
5562
var authRecord = browserCredential.Authenticate(tokenRequestContext);
63+
64+
Directory.CreateDirectory(Path.GetDirectoryName(_tokenPath)!);
5665
using var authRecordStream = File.OpenWrite(_tokenPath);
5766
authRecord.Serialize(authRecordStream);
67+
5868
return browserCredential;
5969
}
6070
}

MsGraphSamples.Services/GraphDataService.cs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.Graph;
55
using Microsoft.Graph.Models;
66
using Microsoft.Kiota.Abstractions;
7-
using Microsoft.Kiota.Abstractions.Serialization;
87
using System.Net;
98

109
namespace MsGraphSamples.Services;
@@ -16,7 +15,7 @@ public interface IGraphDataService
1615

1716
Task<User?> GetUserAsync(string[] select, string? id = null);
1817
Task<int?> GetUsersRawCountAsync(string filter, string search);
19-
18+
2019
Task<ApplicationCollectionResponse?> GetApplicationCollectionAsync(string[] select, string? filter = null, string[]? orderBy = null, string? search = null, ushort top = 999);
2120
Task<ServicePrincipalCollectionResponse?> GetServicePrincipalCollectionAsync(string[] select, string? filter = null, string[]? orderBy = null, string? search = null, ushort top = 999);
2221
Task<DeviceCollectionResponse?> GetDeviceCollectionAsync(string[] select, string? filter = null, string[]? orderBy = null, string? search = null, ushort top = 999);
@@ -44,22 +43,6 @@ public class GraphDataService(GraphServiceClient graphClient) : IGraphDataServic
4443

4544
public string? LastUrl { get; private set; } = null;
4645

47-
public Task<TCollectionResponse?> GetNextPageAsync<TCollectionResponse>(TCollectionResponse? collectionResponse) where TCollectionResponse : BaseCollectionPaginationCountResponse, new()
48-
{
49-
if (collectionResponse?.OdataNextLink == null)
50-
{
51-
return Task.FromResult<TCollectionResponse?>(null);
52-
}
53-
54-
var nextPageRequestInformation = new RequestInformation
55-
{
56-
HttpMethod = Method.GET,
57-
UrlTemplate = collectionResponse.OdataNextLink,
58-
};
59-
60-
return _graphClient.RequestAdapter.SendAsync(nextPageRequestInformation, parseNode => new TCollectionResponse());
61-
}
62-
6346
public Task<User?> GetUserAsync(string[] select, string? id = null)
6447
{
6548
return id == null
@@ -82,6 +65,10 @@ public class GraphDataService(GraphServiceClient graphClient) : IGraphDataServic
8265
LastUrl = WebUtility.UrlDecode(requestInfo.URI.AbsoluteUri);
8366
return _graphClient.RequestAdapter.SendPrimitiveAsync<int?>(requestInfo);
8467
}
68+
public Task<TCollectionResponse?> GetNextPageAsync<TCollectionResponse>(TCollectionResponse? collectionResponse) where TCollectionResponse : BaseCollectionPaginationCountResponse, new()
69+
{
70+
return collectionResponse.GetNextPageAsync(_graphClient.RequestAdapter);
71+
}
8572

8673

8774
public Task<ApplicationCollectionResponse?> GetApplicationCollectionAsync(string[] select, string? filter = null, string[]? orderBy = null, string? search = null, ushort top = 999)

MsGraphSamples.Services/AsyncEnumerableGraphExtensions.cs renamed to MsGraphSamples.Services/GraphExtensions.cs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace MsGraphSamples.Services;
77

8-
public static class AsyncEnumerableGraphExtensions
8+
public static class GraphExtensions
99
{
1010
/// <summary>
1111
/// Transform a generic RequestInformation into an AsyncEnumerable to efficiently iterate through the collection in case there are several pages.
@@ -35,6 +35,7 @@ public static async IAsyncEnumerable<TEntity> ToAsyncEnumerable<TEntity, TCollec
3535
where TCollectionResponse : BaseCollectionPaginationCountResponse, new()
3636
{
3737
var collectionResponse = await collectionResponseTask.ConfigureAwait(false);
38+
3839
await foreach (var item in collectionResponse.ToAsyncEnumerable<TEntity, TCollectionResponse>(requestAdapter, countAction))
3940
{
4041
yield return item;
@@ -56,31 +57,48 @@ public static async IAsyncEnumerable<TEntity> ToAsyncEnumerable<TEntity, TCollec
5657
{
5758
countAction?.Invoke(collectionResponse?.OdataCount);
5859

59-
while (true)
60+
while (collectionResponse != null)
6061
{
61-
var entities = collectionResponse?.BackingStore.Get<List<TEntity>>("value") ?? Enumerable.Empty<TEntity>();
62+
var entities = collectionResponse.GetValue<TEntity>() ?? [];
6263
foreach (var entity in entities)
6364
{
6465
yield return entity;
6566
}
6667

67-
if (collectionResponse?.OdataNextLink == null)
68-
{
69-
break;
70-
}
68+
collectionResponse = await collectionResponse.GetNextPageAsync(requestAdapter);
69+
}
70+
}
7171

72-
var nextPageRequestInformation = new RequestInformation
73-
{
74-
HttpMethod = Method.GET,
75-
UrlTemplate = collectionResponse.OdataNextLink,
76-
};
72+
public static List<TEntity>? GetValue<TEntity>(this BaseCollectionPaginationCountResponse collectionResponse) where TEntity : Entity
73+
{
74+
return collectionResponse.BackingStore.Get<List<TEntity>>("value");
75+
}
7776

78-
collectionResponse = await requestAdapter
79-
.SendAsync(nextPageRequestInformation, parseNode => new TCollectionResponse())
80-
.ConfigureAwait(false);
81-
}
77+
public static async Task<TCollectionResponse?> GetNextPageAsync<TCollectionResponse>(this TCollectionResponse? collectionResponse, IRequestAdapter requestAdapter)
78+
where TCollectionResponse : BaseCollectionPaginationCountResponse, new()
79+
{
80+
if (collectionResponse?.OdataNextLink == null)
81+
return null;
82+
83+
var nextPageRequestInformation = new RequestInformation
84+
{
85+
HttpMethod = Method.GET,
86+
UrlTemplate = collectionResponse.OdataNextLink,
87+
};
88+
var previousCount = collectionResponse.OdataCount;
89+
90+
var nextPage = await requestAdapter
91+
.SendAsync(nextPageRequestInformation, parseNode => new TCollectionResponse())
92+
.ConfigureAwait(false);
93+
94+
// fix count property not present in pages other than the first one
95+
if (nextPage != null)
96+
nextPage.OdataCount = previousCount;
97+
98+
return nextPage;
8299
}
83100

101+
84102
public static async IAsyncEnumerable<TEntity> Batch<TEntity, TCollectionResponse>(this GraphServiceClient graphClient, params RequestInformation[] requests)
85103
where TEntity : Entity
86104
where TCollectionResponse : BaseCollectionPaginationCountResponse, new()

MsGraphSamples.Services/MSGraphSamples.Services.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net8.0-windows10.0.22000.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<UserSecretsId>42125e81-2956-48ae-a133-1633482ae5e8</UserSecretsId>
8+
<SupportedOSPlatformVersion>10.0.22000.0</SupportedOSPlatformVersion>
89
</PropertyGroup>
910

1011
<ItemGroup>

MsGraphSamples.WPF/MsGraphSamples.WPF.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>WinExe</OutputType>
5-
<TargetFramework>net8.0-windows</TargetFramework>
5+
<TargetFramework>net8.0-windows10.0.22000.0</TargetFramework>
66
<Nullable>enable</Nullable>
77
<ImplicitUsings>enable</ImplicitUsings>
88
<UseWPF>true</UseWPF>

MsGraphSamples.WPF/ViewModels/MainViewModel.cs

Lines changed: 17 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,10 @@ public async Task Init()
109109
await Load();
110110
}
111111

112-
113112
[RelayCommand]
114113
private async Task Load()
115114
{
116-
DirectoryObjects = await IsBusyWrapper1(async () => SelectedEntity switch
115+
await IsBusyWrapper(async () => SelectedEntity switch
117116
{
118117
"Users" => await graphDataService.GetUserCollectionAsync(SplittedSelect, Filter, SplittedOrderBy, Search),
119118
"Groups" => await graphDataService.GetGroupCollectionAsync(SplittedSelect, Filter, SplittedOrderBy, Search),
@@ -130,38 +129,39 @@ private async Task DrillDown()
130129
{
131130
ArgumentNullException.ThrowIfNull(SelectedObject);
132131

133-
DirectoryObjects = await IsBusyWrapper1(async () =>
132+
await IsBusyWrapper(async () =>
134133
{
135134
OrderBy = string.Empty;
136135
Filter = string.Empty;
137136
Search = string.Empty;
138137

139-
return SelectedEntity switch
138+
return DirectoryObjects switch
140139
{
141-
"Users" => await graphDataService.GetTransitiveMemberOfAsGroupCollectionAsync(SelectedObject.Id!, SplittedSelect),
142-
"Groups" => await graphDataService.GetTransitiveMembersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
143-
"Applications" => await graphDataService.GetApplicationOwnersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
144-
"ServicePrincipals" => await graphDataService.GetServicePrincipalOwnersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
145-
"Devices" => await graphDataService.GetDeviceOwnersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
146-
_ => throw new NotImplementedException("Can't find selected entity")
140+
UserCollectionResponse userCollection => await graphDataService.GetTransitiveMemberOfAsGroupCollectionAsync(SelectedObject.Id!, SplittedSelect),
141+
GroupCollectionResponse groupCollection => await graphDataService.GetTransitiveMembersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
142+
ApplicationCollectionResponse applicationCollection => await graphDataService.GetApplicationOwnersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
143+
ServicePrincipalCollectionResponse servicePrincipalCollection => await graphDataService.GetServicePrincipalOwnersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
144+
DeviceCollectionResponse deviceCollection => await graphDataService.GetDeviceOwnersAsUserCollectionAsync(SelectedObject.Id!, SplittedSelect),
145+
_ => throw new NotImplementedException("Can't find Entity Type")
147146
};
148147
});
149148
}
150149

151150
private bool CanGoNextPage => DirectoryObjects?.OdataNextLink is not null;
152151
[RelayCommand(CanExecute = nameof(CanGoNextPage))]
153-
private async Task LoadNextPage()
152+
private Task LoadNextPage()
154153
{
155-
//DirectoryObjects = await graphDataService.GetNextPageAsync(DirectoryObjects);
156-
DirectoryObjects = DirectoryObjects switch
154+
//return IsBusyWrapper(() => graphDataService.GetNextPageAsync(DirectoryObjects));
155+
156+
return IsBusyWrapper(async () => DirectoryObjects switch
157157
{
158158
UserCollectionResponse userCollection => await graphDataService.GetNextPageAsync(userCollection),
159159
GroupCollectionResponse groupCollection => await graphDataService.GetNextPageAsync(groupCollection),
160160
ApplicationCollectionResponse applicationCollection => await graphDataService.GetNextPageAsync(applicationCollection),
161161
ServicePrincipalCollectionResponse servicePrincipalCollection => await graphDataService.GetNextPageAsync(servicePrincipalCollection),
162162
DeviceCollectionResponse deviceCollection => await graphDataService.GetNextPageAsync(deviceCollection),
163-
_ => throw new NotImplementedException("Can't find selected entity")
164-
};
163+
_ => throw new NotImplementedException("Can't find Entity Type")
164+
});
165165
}
166166

167167
[RelayCommand]
@@ -199,48 +199,9 @@ private void Logout()
199199
authService.Logout();
200200
App.Current.Shutdown();
201201
}
202-
private async Task<BaseCollectionPaginationCountResponse?> IsBusyWrapper1(Func<Task<BaseCollectionPaginationCountResponse?>> getDirectoryObjects)
203-
{
204-
IsBusy = true;
205-
_stopWatch.Restart();
206202

207-
// Sending message to generate DataGridColumns according to the selected properties
208-
WeakReferenceMessenger.Default.Send(SplittedSelect);
209-
try
210-
{
211-
return await getDirectoryObjects();
212-
213-
//SelectedEntity = DirectoryObjects switch
214-
//{
215-
// UserCollectionResponse => "Users",
216-
// GroupCollectionResponse => "Groups",
217-
// ApplicationCollectionResponse => "Applications",
218-
// ServicePrincipalCollectionResponse => "ServicePrincipals",
219-
// DeviceCollectionResponse => "Devices",
220-
// _ => SelectedEntity,
221-
//};
222-
}
223-
catch (ODataError ex)
224-
{
225-
Task.Run(() => System.Windows.MessageBox.Show(ex.Message, ex.Error?.Message)).Await();
226-
return null;
227-
}
228-
catch (ApiException ex)
229-
{
230-
Task.Run(() => System.Windows.MessageBox.Show(ex.Message, ex.Source)).Await();
231-
return null;
232-
}
233-
finally
234-
{
235-
_stopWatch.Stop();
236-
OnPropertyChanged(nameof(ElapsedMs));
237-
OnPropertyChanged(nameof(LastUrl));
238-
IsBusy = false;
239-
}
240-
241-
}
242203

243-
private async Task IsBusyWrapper(Task<BaseCollectionPaginationCountResponse> getDirectoryObjects)
204+
private async Task IsBusyWrapper(Func<Task<BaseCollectionPaginationCountResponse?>> getDirectoryObjects)
244205
{
245206
IsBusy = true;
246207
_stopWatch.Restart();
@@ -250,7 +211,7 @@ private async Task IsBusyWrapper(Task<BaseCollectionPaginationCountResponse> get
250211

251212
try
252213
{
253-
DirectoryObjects = await getDirectoryObjects;
214+
DirectoryObjects = await getDirectoryObjects();
254215

255216
SelectedEntity = DirectoryObjects switch
256217
{

MsGraphSamples.WPF/Views/MainWindow.xaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@
151151
Grid.Row="0"
152152
Grid.Column="2"
153153
Margin="6,0">
154-
<!--<TextBlock FontWeight="Bold" Text="{Binding DirectoryObjects, Converter={StaticResource DirectoryObjectsCount}, StringFormat=Entities: {0}, FallbackValue='Entities:'}" />-->
154+
<TextBlock FontWeight="Bold">
155+
<Run Text="Entities: "/>
156+
<Run Text="{Binding DirectoryObjects.Value.Count, Mode=OneWay}"/>
157+
<Run Text=" / "/>
158+
<Run Text="{Binding DirectoryObjects.OdataCount, Mode=OneWay}" />
159+
</TextBlock>
155160
<ComboBox
156161
Width="120"
157162
ItemsSource="{Binding Entities}"

MsGraphSamples.WinUI/MsGraphSamples.WinUI.csproj

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>WinExe</OutputType>
44
<TargetFramework>net8.0-windows10.0.22000.0</TargetFramework>
55
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
6-
<RootNamespace>MSGraphSamples.WinUI</RootNamespace>
76
<ApplicationManifest>app.manifest</ApplicationManifest>
87
<Platforms>x86;x64;arm64</Platforms>
9-
<RuntimeIdentifiers Condition="$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)')) &gt;= 8">win-x86;win-x64;win-arm64</RuntimeIdentifiers>
10-
<RuntimeIdentifiers Condition="$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)')) &lt; 8">win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
8+
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
119
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
1210
<UseWinUI>true</UseWinUI>
13-
<EnableMsixTooling>false</EnableMsixTooling>
11+
<EnableMsixTooling>true</EnableMsixTooling>
1412
<ImplicitUsings>enable</ImplicitUsings>
1513
<Nullable>enable</Nullable>
1614
<PlatformTarget>x64</PlatformTarget>
@@ -44,6 +42,10 @@
4442
<ProjectReference Include="..\MsGraphSamples.Services\MsGraphSamples.Services.csproj" />
4543
</ItemGroup>
4644

45+
<ItemGroup>
46+
<Folder Include="Properties\PublishProfiles\" />
47+
</ItemGroup>
48+
4749

4850
<!--
4951
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution

0 commit comments

Comments
 (0)