How to mock Microsoft Graph APIs GraphServiceClient using moq - microsoft-graph-api

My project is using GraphServiceClient to get the user Group Names using the below code.This is also using Microsoft.Identity.Web package so GraphServiceClient is injected through constructor.
var group = await graphClient.Me.TransitiveMemberOf
.Request()
.GetAsync();
The group variable is then used to get the DisplayName of the group.
I want to unit test the above code using NUnit and Moq.
var mockAuthProvider = new Mock<IAuthenticationProvider>();
var mockHttpProvider = new Mock<IHttpProvider>();
var mockGraphClient = new Mock<GraphServiceClient>(mockAuthProvider.Object, mockHttpProvider.Object);
mockGraphClient.Setup(c => c.Me.TransitiveMemberOf.Request().GetAsync(CancellationToken.None)).ReturnsAsync(???);
The ReturnAsync will return IUserTransitiveMemberOfCollectionWithReferencesPage, but how can I give a default value for it so I can test the rest of the method which actually gets the displayName
Thanks in advance.

Create a new instance of UserTransitiveMemberOfCollectionWithReferencesPage and add a new Group item to the current page.
UserTransitiveMemberOfCollectionWithReferencesPage page = new
UserTransitiveMemberOfCollectionWithReferencesPage
{
AdditionalData = new Dictionary<string, object>(),
};
page.Add(new Group { DisplayName = "MyName" });
Return page in ReturnsAsync method
mockGraphClient.Setup(c => c.Me.TransitiveMemberOf.Request().GetAsync(CancellationToken.None))
.ReturnsAsync(() => page);

Related

Microsoft.Graph SDK connects to user's OneDrive but Items returns NULL

I'm writing a utility in C# using the Microsoft.Graph SDK that connects and reads a user's OneDrive. I have created an App Registration, granted the application Files.Read.All permissions, and given Admin consent per the documentation.
I am able to connect to the Graph and the metadata for the OneDrive:
List<string> scopes = new List<string>();
scopes.Add("https://graph.microsoft.com/.default");
var authenticationProvider = new MsalAuthenticationProvider(cca, scopes.ToArray());
GraphServiceClient graphClient = new GraphServiceClient(authenticationProvider);
var drive = await graphClient.Users[userId].Drive
.Request()
.GetAsync();
It appears to correctly connect to the OneDrive, as evidenced by the properties that do return correct data like Quota, Owner, etc.
The issue is that the Items object is null, so I can't read any of the drive items:
I tried using the returned drive Id to access the drive directly, but received the same result:
var driveById = await graphClient.Drives[drive.Id]
.Request()
.GetAsync();
The few examples I found don't indicate any additional Request options or missing permissions. So how do I access the OneDrive Items?
The solution for this issue was presented in the comments, so I'm writing it up here for completeness.
ORIGINAL CODE:
var rootDrive = await GraphClient.Users[UserId].Drive.Request().GetAsync();
This returns the metadata of the user's OneDrive, but does not capture the Children. We will need this information later, however, so the final solution uses both this reference and the updated code.
UPDATED CODE:
To do that, you need to reference the drive's Root and its Children:
var driveItems = await GraphClient.Users[UserId].Drive
.Root
.Children
.Request()
.GetAsync();
Doing so returns an IDriveItemChildrenCollectionPage:
PROCESS THE CHILDREN:
For small samples, a standard foreach will work fine, but for larger collections you will need to implement a PageIterator (which I have not done yet). To get the children of this driveItem, you will need the drive Id of the root element as well as the current driveItem.Id:
var children = await GraphClient.Drives[rootDriveId].Items[item.Id].Children.Request().GetAsync()
Putting it altogether, it looks something like this:
public async Task ListOneDrive()
{
var rootDrive = await GraphClient.Users[UserId].Drive.Request().GetAsync();
var driveItems = await GraphClient.Users[UserId].Drive
.Root
.Children
.Request()
.GetAsync();
foreach (var item in driveItems)
{
await ListDriveItem(rootDrive.Id, item);
}
}
public async Task ListDriveItem(string rootDriveId, DriveItem item, int indentLevel = 1)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indentLevel; i++)
{
sb.Append($" ");
}
if (item.Folder != null)
{
Console.WriteLine($"{sb.ToString()}> {item.Name}/");
var children = await GraphClient.Drives[rootDriveId].Items[item.Id].Children.Request().GetAsync();
foreach (var child in children)
{
await (ListDriveItem(rootDriveId, child, indentLevel + 1));
}
}
else if (item.File != null)
{
Console.WriteLine($"{sb.ToString()} {item.Name}");
}
}
This example is from a Console app that uses a recursive call to drill down through all the folders. Both methods really should have a PageIterator as mentioned above.

Exception Azure AD B2C Setting Custom Attribute With GraphServiceClient

Following directions from here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-custom-attributes?pivots=b2c-user-flow
I am able to create and get users fine:
// This works!
var graphServiceClient = new GraphServiceClient(
$"https://graph.microsoft.com/beta",
AuthenticationProvider);
var user = await graphClient.Users[userId].Request().GetAsync();
if (user.AdditionalData == null)
user.AdditionalData = new Dictionary<string, object>();
user.AdditionalData[$"extension_xxxxxxx_Apps] = "TestValue";
// this does not work!
result = await graphClient.Users[user.Id].Request().UpdateAsync(msGraphUser);
For xxxxxxx I tried both the Client ID and Object Id from the b2c-extensions-app in my tenant.
Exception:
Microsoft.Graph.ServiceException: 'Code: Request_BadRequest
Message: The following extension properties are not available: extension_xxxxxxx_Apps.
what am I missing? How can I set a custom attribute from GraphServiceClient?
Thank you
Try creating a "new" user rather getting the existing one. When you call UpdateAsync, B2C will only set the properties that you provide (it won't overwrite the other props with null). This may or may not help, but the thing is, we're doing the same thing you do above except with a "new" User, and it works for us.
User b2cUser = await this.GetOurUser(userId);
var additionalData = new Dictionary<string, object>();
additionalData["extension_id-of-extensions-app-here_Foo"] = "Ice Cream Cone";
var updatedB2cUser = new User
{
AdditionalData = additionalData
};
await this.GraphServiceClient.Users[b2cUser.Id].Request().UpdateAsync(updatedB2cUser);
In practice, we include additional props such as Identities, because we use B2C as an identity provider...so the above is some pared-down code from our B2C wrapper showing just the "custom property" part.
Update
Actually, you may just need to remove the hyphens from your extension-app ID and double-check which one you're using.

Error When creating a GraphServiceClient in Example App from MS

I'm trying to get the MS example application to work for the Graph Beta Webhooks API and it's currently crashing because I've had to to modify some of the example code to remove some obsolete code and I'm not sure what to replace it with.
This is the function:
public static GraphServiceClient GetAuthenticatedClient(string userId, string redirect)
{
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (request) =>
{
var tokenCache = new SampleTokenCache(userId);
// Obsolete code
//var cca = new ConfidentialClientApplication(Startup.ClientId, redirect, new ClientCredential(Startup.ClientSecret), tokenCache.GetMsalCacheInstance(), null);
// New code
var cca2 = ConfidentialClientApplicationBuilder.Create(Startup.ClientId).WithClientSecret(Startup.ClientSecret).WithRedirectUri(redirect).Build();
// Question - how do I pass the tokenCache in here as the userTokenCache ?
// ERROR - With the new code this returns zero accounts presuambly because I haven't passed in a userTokenCache
var accounts = await cca2.GetAccountsAsync();
// Obsolete code
//var authResult = await cca.AcquireTokenSilentAsync(Startup.Scopes, accounts.First());
// New code
var authResult2 = await cca2.AcquireTokenSilent(Startup.Scopes, accounts.First()).ExecuteAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult2.AccessToken);
}));
return graphClient;
}
If I use the ConfidentialClientApplicationBuilder then the GetAccountsAsync() returns an empty collection and I think it's because I haven't passed the tokenCache into the builder. Does anyone know how to fix this code or has anyone got the example App working ?
This is the link to the example App:
https://learn.microsoft.com/en-us/samples/microsoftgraph/aspnet-webhooks-rest-sample/microsoft-graph-aspnet-webhooks/
Thanks
Ed James
You need to login as user so that an account is added to the token cache which is available calling the tokenCache.GetMsalCacheInstance() method.

Asp Net Mvc Identity Multitenancy FindByEmailAsync not working

I'm trying to implements a multitenancy application following step by step the Scott Brady post here:
https://www.scottbrady91.com/ASPNET-Identity/Quick-and-Easy-ASPNET-Identity-Multitenancy
I'm not really sure where I should put these code lines:
var context = new ApplicationUserDbContext<ApplicationUser>("DefaultConnection");
var userStore = new ApplicationUserStore<ApplicationUser>(context) { TenantId = 1 };
var userManager = new UserManager<ApplicationUser, string>(userStore);
I have tried to use this into my login action in AccountController:
var context = new ApplicationDbContext();
var userStore = new ApplicationUserStore<ApplicationUser>(context) { TenantId = tenantId };
UserManager = new ApplicationUserManager(userStore);
SignInManager.UserManager = UserManager;
ApplicationUser userApp =await UserManager.FindByEmailAsync(model.Email);
But UserManager.FindByEmailAsync always returns null, any idea of what I should do to get this working? My real objetive is to SingIn User but always return Failure. And I think the problem is finding the ApplicationUser.
If you need additional information please, let me know, thanks in advance.
Your are Using the Classes directly. Give a try with this way...and make sure that you have the user with that gmail that you are providing.
var context = new ApplicationDbContext();
var store = new UserStore<ApplicationUser>(context);
var manager = new ApplicationUserManager(store);
ApplicationUser userApp=await manager.FindByEmailAsync(model.Email);

ServiceStack - Posting multiple files with one POST request

I'm struggling with this issue for several hours, and I can't find any solution.
Does someone used ServiceStack to upload multiple files with one POST request?
I was trying to use PostFile:
FileInfo fi = new FileInfo("ExampleData\\XmlAPI.xml");
var client = new XmlServiceClient("http://localhost:1337");
client.PostFile<DefaultResponse>("/test", fi, "application/xml");
But here I am able to add only one file to the request.
My second shot was to use LocalHttpWebRequestFilter but inside there is only a extension method which also allows to post only one file.
Multiple File Upload APIs have been added to all .NET Service Clients in v4.0.54 that allow you to easily upload multiple streams within a single HTTP request. It supports populating Request DTO with any combination of QueryString and
POST'ed FormData in addition to multiple file upload data streams:
using (var stream1 = uploadFile1.OpenRead())
using (var stream2 = uploadFile2.OpenRead())
{
var client = new JsonServiceClient(baseUrl);
var response = client.PostFilesWithRequest<MultipleFileUploadResponse>(
"/multi-fileuploads?CustomerId=123",
new MultipleFileUpload { CustomerName = "Foo,Bar" },
new[] {
new UploadFile("upload1.png", stream1),
new UploadFile("upload2.png", stream2),
});
}
Or using only a Typed Request DTO. The JsonHttpClient also includes async equivalents for each of the new
PostFilesWithRequest APIs:
using (var stream1 = uploadFile1.OpenRead())
using (var stream2 = uploadFile2.OpenRead())
{
var client = new JsonHttpClient(baseUrl);
var response = await client.PostFilesWithRequestAsync<MultipleFileUploadResponse>(
new MultipleFileUpload { CustomerId = 123, CustomerName = "Foo,Bar" },
new[] {
new UploadFile("upload1.png", stream1),
new UploadFile("upload2.png", stream2),
});
}

Resources