I have a basic razor pages web application that retrieves the user and photo using Microsoft Graph. I'm using ClientSecretCredential and everything works as expected using Microsoft.Graph V4. The user and photo is retrieved successfully and I can cast the photo as a MemoryStream to display on the page.
This works using version 4
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-GraphTest-f0c950ea-9c29-4d6d-af44-7711be58959f</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.2" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.2" NoWarn="NU1605" />
<PackageReference Include="Microsoft.Graph" Version="4.54.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.7.1" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="3.7.1" />
</ItemGroup>
</Project>
public async Task OnGet()
{
var options = new ClientSecretCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var credentials = new ClientSecretCredential(<tenantid>, <clientid>, <clientsecret>, options);
var graphServiceClient = new GraphServiceClient(credentials, new string[] { "https://graph.microsoft.com/.default" });
var user = await graphServiceClient.Users[<userid>].Request().GetAsync();
ViewData["Name"] = user.DisplayName;
using var photoStream = await graphServiceClient.Users[<userid>].Photo.Content.Request().GetAsync();
ViewData["Photo"] = Convert.ToBase64String(((MemoryStream?)photoStream)?.ToArray());
}
However as soon as I upgrade Microsoft.Graph to version 5 and change the graph calling syntax (ie remove .Request() to satisfy compilation) the wrong photo stream is returned and I get an InvalidCastException error.
This now breaks on row ViewData["Photo"] = Convert.ToBase64String(((MemoryStream?)photoStream)?.ToArray());
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-GraphTest-f0c950ea-9c29-4d6d-af44-7711be58959f</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.2" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.2" NoWarn="NU1605" />
<PackageReference Include="Microsoft.Graph" Version="5.71.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.7.1" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="3.7.1" />
</ItemGroup>
</Project>
public async Task OnGet()
{
var options = new ClientSecretCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var credentials = new ClientSecretCredential(<tenantid>, <clientid>, <clientsecret>, options);
var graphServiceClient = new GraphServiceClient(credentials, new string[] { "https://graph.microsoft.com/.default" });
var user = await graphServiceClient.Users[<userid>].GetAsync();
ViewData["Name"] = user.DisplayName;
using var photoStream = await graphServiceClient.Users[<userid>].Photo.Content.GetAsync();
ViewData["Photo"] = Convert.ToBase64String(((MemoryStream?)photoStream)?.ToArray());
}
System.InvalidCastException HResult=0x80004002 Message=Unable to cast object of type 'Http2ReadStream' to type 'System.IO.MemoryStream'. Source=System.Private.CoreLib StackTrace: at System.Runtime.CompilerServices.CastHelpers.ChkCast_Helper(Void* toTypeHnd, Object obj) at GraphTest.Pages.IndexModel.d__2.MoveNext() in C:\Users\ojmcf\source\repos\GraphTest\GraphTest\Pages\Index.cshtml.cs:line 43 at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.NonGenericTaskHandlerMethod.d__2.MoveNext() at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.d__29.MoveNext() at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.d__31.MoveNext()
I have not been able to find anyone else with a similar issue but surely I can't be the only one or others have found a workaround?
In version 5, the response from graphServiceClient.Users[<userid>].Photo.Content.GetAsync()
returns an Http2ReadStream
instead of a MemoryStream
, which causes the cast to MemoryStream
to fail.
The error is because the Http2ReadStream
isn't directly castable to MemoryStream
.
To resolve the error, you need to read the content stream (which is now an Http2ReadStream
) into a MemoryStream
before converting it to Base64
:
class Program
{
static async Task Main(string[] args)
{
string tenantId = "TenantID";
string clientId = "ClientID";
string clientSecret = "Secret";
string userId = "UserID";
var options = new ClientSecretCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// Instantiate the ClientSecretCredential with provided credentials
var credentials = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
// Create the GraphServiceClient to interact with Microsoft Graph API
var graphServiceClient = new GraphServiceClient(credentials, new string[] { "https://graph.microsoft.com/.default" });
try
{
// Retrieve the user information
var user = await graphServiceClient.Users[userId].GetAsync();
Console.WriteLine($"User Name: {user.DisplayName}");
// Retrieve the user's photo as a stream
var photoStream = await graphServiceClient.Users[userId].Photo.Content.GetAsync();
// Copy the photo stream into a MemoryStream
using (var memoryStream = new MemoryStream())
{
await photoStream.CopyToAsync(memoryStream);
// Convert the photo content to Base64 string
string base64Photo = Convert.ToBase64String(memoryStream.ToArray());
Console.WriteLine("User Photo (Base64):");
Console.WriteLine(base64Photo); // Prints the Base64 encoded photo
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}