"The audience claim value is invalid for current resource" when using ChunkedUploadProvider for Attachment in Microsoft Graph Client API - microsoft-graph-api

I am trying to use the following code, but am getting "Message: The audience claim value is invalid for current resource. Audience claim is 'https://graph.microsoft.com', request url is 'https://outlook.office.com/api/beta/Users..."
I get it on the provider.GetUploadChunkRequests(); call below:
AttachmentItem attachmentItem= new AttachmentItem
{
Name = [Name],
AttachmentType = AttachmentType.File,
Size = [Size]
};
var session = graphClient.Users[USEREMAIL].Messages[MESSAGEID].Attachments.CreateUploadSession(attachmentItem).Request().PostAsync().Result;
var stream = new MemoryStream(BYTEARRAY);
var maxSizeChunk = DEFAULT_CHUNK_SIZE;
var provider = new ChunkedUploadProvider(session, graphClient, stream, maxSizeChunk);
var chunkRequests = provider.GetUploadChunkRequests();
(I am using the graphClient to send emails successfully, and have also used it to upload large files using the uploadSession method)

From Andrue Eastman on GitHub:
You are most likely getting the error because of using the ChunkedUploadPorvider instead of using the FileUploadTask to upload the attachment which is setting the Auth header to cause the error you are receiving.
To use the file upload task, follow the following steps
First create an upload session and handing it over to the task as illustrated.
// Create task
var maxSliceSize = 320 * 1024; // 320 KB - Change this to your chunk size. 4MB is the default.
LargeFileUploadTask<FileAttachment> largeFileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession, stream, maxSliceSize);
Create an upload monitor (optional)
// Setup the progress monitoring
IProgress<long> progress = new Progress<long>(progress =>
{
Console.WriteLine($"Uploaded {progress} bytes of {stream.Length} bytes");
});
The service only returns location URI which can be read off from the result object as follows.
UploadResult<FileAttachment> uploadResult = null;
try
{
uploadResult = await largeFileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
Console.WriteLine(uploadResult.Location);//the location of the object
}
}
catch (ServiceException e)
{
Console.WriteLine(e.Message);
}

Related

How to do a POST batch request in Miscrosoft Graph .NET SDK

I'm trying to do a batch request using MS Graph .NET SDK as shown here: https://learn.microsoft.com/en-us/graph/sdks/batch-requests?tabs=csharp
The only problem is that when I run the code, nothing happens.
I'm trying to move a set of emails (stored in a list) to another mail folder.
Am I missing anything?
The move request is here https://learn.microsoft.com/en-us/graph/api/message-move?view=graph-rest-1.0&tabs=http
When used in a single query it works, but not when batching.
Below you will find the code, in this case I'm looping to 20 just to test as 20 is the maximum queries per batch.
Thanks in advance.
for (int i = 0; i < 20; i++)
{
var mail = invalidMessages[i];
var userRequest = client.Me.Messages[mail.Id]
.Move(failureFolderID)
.Request();
requestID = batchRequestContent.AddBatchRequestStep(userRequest);
}
var returnedResponse = await client.Batch.Request().PostAsync(batchRequestContent);
EDIT: I tried to change the method to POST
userRequest.Method = System.Net.Http.HttpMethod.Post;
but I get a ServiceException: 'Code: BadRequest
Message: Write request id : fe23b1c1-663d-4499-829a-291d04a12b48 does not contain Content-Type header or body.'
The Microsoft Graph message-move API call you are attempting to use is a POST request
The Microsoft Batch API handles POST requests differently than the other API methods.
As per https://learn.microsoft.com/en-us/graph/sdks/batch-requests?tabs=csharp
POST requests are handled a bit differently.
The SDK request builders generate GET requests, so
you must get the HttpRequestMessage and convert to a POST
To have a successful post with the batch API you need to
Create an HttpRequestMessage
provide a value for the HttpRequestMessage's Content property which houses the POST requests payload
So if I applied this to your code I would first create a class to represent the POST payload for the message-move API. As per https://learn.microsoft.com/en-us/graph/api/message-move?view=graph-rest-1.0&tabs=http
the POST API has one property called destinationId
destinationId - The destination folder ID, or a well-known folder name. For a list of supported well-known folder names, see mailFolder resource type.
public class MailMovePayload
{
public string destinationId { get; set; }
}
then I would use an instance of this class in this modified version of you code
string str = events.Content.ReadAsStringAsync().Result;
events.Method = HttpMethod.Post;
for (int i = 0; i < 20; i++)
{
var mail = invalidMessages[i];
//get the request message object from your request
var userRequestMessage = client.Me.Messages[mail.Id]
.Move(failureFolderID)
.GetHttpRequestMessage();
//set the message API method
userRequestMessage.Method = HttpMethod.Post;
//create the payload, I am assuming failureFolderID is
//the name of the folder where the mail will be moved to
var payloadData = new MailMovePayload { destinationId = failureFolderID };
//make the JSON payload for the request message
userRequestMessage.Content = new StringContent(JsonConvert.SerializeObject(payloadData), Encoding.UTF8, "application/json")
requestID = batchRequestContent.AddBatchRequestStep(userRequestMessage);
}
var returnedResponse = await client.Batch.Request().PostAsync(batchRequestContent);
return httpRequestMessage;
}

Microsoft Graph API using uploadsession not able to recieve email with attachments

Email Not been recieved with attachments when I try to use Uploadsession using Graph API. can someone help me uderstand why this is happening. I have not recieved any error.
Message draft = await graphServiceClient.Users["UserID"].Messages.Request().AddAsync(email);
//Message draft = graphServiceClient.Users["UserID"].Mailfolders.Drafts.Messages.Request().AddAsync(email);
var stream = System.IO.File.Open(#"C:\attach\DYN28_6579332_33242556.csv", System.IO.FileMode.Open, FileAccess.Read, FileShare.None);
var attachmentItem = new AttachmentItem
{
AttachmentType = AttachmentType.File,
Name = "DYN28_6579332_33242556.csv",
Size = stream.Length
};
var uploadSession = await graphServiceClient.Users["Userid"].Messages[draft.Id]
.Attachments
.CreateUploadSession(attachmentItem)
.Request()
.PostAsync();
var maxSlicesize = 320 * 1024;
var largeFileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession, stream, maxSlicesize);
IProgress<long> progress = new Progress<long>(prog => {
Console.WriteLine($"Uploaded {prog} bytes of {stream.Length} bytes");
});
// Upload the file
var uploadResult = await largeFileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
// The ItemResponse object in the result represents the
// created item.
//Console.WriteLine($"Upload complete, item ID: {uploadResult.ItemResponse.Id}");
Console.WriteLine("upload completed");
}
Finally sending email with
await graphServiceClient.Users["userid"].Messages[draft.Id]
.Send()
.Request()
.PostAsync();
There is a limit of 4MB on a single request in the Graph API. To send larger attachments, you need to first create an upload session against the email message/calendar event, and upload your attachment in a number of requests as part of this session. AFAIK each of the smaller POST requests would also need to stay below the 4MB limit.
You can find more detailed documentation and a sample walkthrough here.
POST https://graph.microsoft.com/v1.0/me/messages/AAMkADI5MAAIT3drCAAA=/attachments/createUploadSession
Content-type: application/json
{
"AttachmentItem": {
"attachmentType": "file",
"name": "flower",
"size": 3483322
}
}

Preview image attachments in ChatMessage

We are using the ms graph api to post messages to a teams channel from a internal desktop application. The main purpose is to attach images to the message. We upload the image files into the one-drive folder of the channel as shown below.
var uploadProps = new DriveItemUploadableProperties
{
ODataType = null,
AdditionalData = new Dictionary<string, object>
{
{ "#microsoft.graph.conflictBehavior", "replace" }
}
};
var session = await graphClient.Drives[driveId]
.Items[parentId].ItemWithPath(fileName).CreateUploadSession(uploadProps).Request().PostAsync(token);
int maxSliceSize = 320 * 1024;
var fileUploadTask =
new LargeFileUploadTask<DriveItem>(session, fileStream, maxSliceSize);
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(reportAsync);
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
return uploadResult.ItemResponse;
}
We then send a message to the channel and attach the images uploaded previously as reference attachments.
var chatMsg = new ChatMessage();
chatMsg.Body = new ItemBody();
chatMsg.Body.ContentType = BodyType.Html;
chatMsg.Body.Content = msg + " " + string.Join(" ", attachments.Select(d => $"<attachment id=\"{parseEtag(d.ETag)}\"></attachment>"));
chatMsg.Attachments = attachments.Select(d => new ChatMessageAttachment()
{
Id = parseEtag(d.ETag),
ContentType = "reference",
ContentUrl = d.WebUrl,
Name = d.Name
});
return await this.graphClient.Teams[teamId].Channels[channelId].Messages
.Request()
.AddAsync(chatMsg, token);
The problem is that the message only shows the names of the attachments with no preview as seen in the message at the bottom. We want to have a preview as seen (top message) when attaching a file within the teams application.
We've tried to set the thumbnailurl property of the attachment to the thumbnail url fetched from the ms-graph api with no success.
We've uploaded a file using the teams application (with preview) and then created an identical message with the same file (same driveitem id) in our application (show's no preview). Then we fetched both messages using the graph api and could not discern any differences between the two besides the message id's ofc.
We've scoured these forums, the ms documentations and even suggestion pages and found nothing.
We have been able to show previews separately in the body of the message referencing the thumbnail urls and in messagecards but ideally we want the preview directly in the attachments.
EDIT
The thumbnail urls seem to expire after 24 hours and are therefor not a great solution.
We managed to solve exactly this problem using the Simple Upload Api, with the added ?$expand=thumbnails query parameter. I haven't tried but the query param ought to work for the endpoint you're using as well.
Pick a size from the ThumbnailSet in the upload response and add it to the body of your message as an image tag. See below:
// channel, file, extractIdFromEtag, message omitted for brevity.
// PUT /groups/{group-id}/drive/items/{parent-id}:/{filename}:/content
const uploadUrl = `https://graph.microsoft.com/beta/groups/${channel.teamId}/drive/items/root:/${channel.displayName}/${file.name}:/content?$expand=thumbnails`;
const res = await this.http.put(uploadUrl, file).toPromise(); // FYI Using Angular http service
const attachment = {
id: extractIdFromEtag(res.eTag),
contentType: 'reference',
contentUrl: res.webUrl,
name: res.name,
thumbnailUrl: res.webUrl
};
const postBody = {
subject: null,
body: {
contentType: 'html',
content: message
},
};
// This is what makes the image show in the message as if posted from teams
postBody.body.content += `<br><br><img src="${res.thumbnails[0].large.url}" alt="${res.name}"/>`;
const messageUrl = `https://graph.microsoft.com/beta/teams/${channel.teamId}/channels/${channel.id}/messages`;
const result = await this.http.post(messageUrl, postBody).toPromise();
// Done
You can also keep adding the attachment as you already do, if you want the original image attached as a file, as well as showing the image preview in the message.

How to convert Office files to PDF using Microsoft Graph

I'm looking for a way to convert Office files to PDF.
I found out that Microsoft Graph could be used.
I'm trying to download converted PDF using Microsoft Graph from OneDrive.
I'd like to convert .docx to .pdf.
However, when I sent the following request, I did not receive a response even if I waited.
GET https://graph.microsoft.com/v1.0/users/{id}/drive/root:/test.docx:/content?format=pdf
Also, the error code is not returned.
If syntax is wrong, an error code will be returned as expected.
It will not return only when it is correct.
In addition, I can download the file if I do not convert.
GET https://graph.microsoft.com/v1.0/users/{id}/drive/root:/test.docx:/content
Is my method wrong or else I need conditions?
If possible, please give me sample code that you can actually do.
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.BaseAddress = new Uri(graphUrl);
var result = await client.GetAsync("/v1.0/users/xxxxxxxxxxxxxxxxxxxxxxxxx/drive/root:/test.docx:/content?format=pdf");
:
I would like to elaborate a bit Marc's answer by providing a few examples for HttpClient.
Since by default for HttpClient HttpClientHandler.AllowAutoRedirect property is set to True there is no need to explicitly follow HTTP redirection headers and the content could be downloaded like this:
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.BaseAddress = new Uri("https://graph.microsoft.com");
var response = await client.GetAsync($"/v1.0/drives/{driveId}/root:/{filePath}:/content?format=pdf");
//save content into file
using (var file = System.IO.File.Create(fileName))
{
var stream = await response.Content.ReadAsStreamAsync();
await stream.CopyToAsync(file);
}
}
In case if follow HTTP redirection is disabled, to download the converted file, your app must follow the Location header in the response as demonstrated below:
var handler = new HttpClientHandler()
{
AllowAutoRedirect = false
};
using (HttpClient client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.BaseAddress = new Uri("https://graph.microsoft.com");
var response = await client.GetAsync($"/v1.0/drives/{driveId}/root:/{filePath}:/content?format=pdf");
if(response.StatusCode == HttpStatusCode.Redirect)
{
response = await client.GetAsync(response.Headers.Location); //get the actual content
}
//save content into file
using (var file = System.IO.File.Create(fileName))
{
var stream = await response.Content.ReadAsStreamAsync();
await stream.CopyToAsync(file);
}
}
The API doesn't return the converted content directly, it returns a link to the converted file. From the documentation:
Returns a 302 Found response redirecting to a pre-authenticated download URL for the converted file.
To download the converted file, your app must follow the Location header in the response.
Pre-authenticated URLs are only valid for a short period of time (a few minutes) and do not require an Authorization header to access.
You need to capture the 302 and make a 2nd call to the URI in the Location header in order to download the converted file.

Why am I getting an Access forbidden error when using the youtube api?

When uploading videos to YouTube using the YouTube api v3, I get the following error:
Google.Apis.Requests.RequestError
Access forbidden. The request may not be properly authorized. [403]
Errors [
Message[Access forbidden. The request may not be properly authorized.] Location[ - ] Reason[forbidden] Domain[youtube.common]
]
The app will successfully upload a couple videos, then randomly throws this 403 error.
The quota limit is 1 millon queries per day, I have only used about 1%.
There are approximately 1200 videos already on the channel that were uploaded using the same app, now all of a sudden in the last few days I am getting this error. I've tried uploading videos to another channel and had no problems, so it looks as though the issue is specific to this one channel. Seems as though it's being throttled, but I don't know why?
I have not contravened any terms of service, the videos are original, the account is in good standing and I'm within the quota limit.
If there are any other limits, why does Google not document it in the api refrence? That way we can design our apps according to policy.
It makes it impossible to build an app if you follow spec and then Google have a different set of rules on the backend and you are left guessing what you are and aren't allowed to do.
Using this code:
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Upload;
using Google.Apis.Util.Store;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
namespace Google.Apis.YouTube.Samples
{
/// <summary>
/// YouTube Data API v3 sample: upload a video.
/// Relies on the Google APIs Client Library for .NET, v1.7.0 or higher.
/// See https://developers.google.com/api-client-library/dotnet/get_started
/// </summary>
internal class UploadVideo
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("YouTube Data API: Upload Video");
Console.WriteLine("==============================");
try
{
new UploadVideo().Run().Wait();
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine("Error: " + e.Message);
}
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
private async Task Run()
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = "Default Video Title";
video.Snippet.Description = "Default Video Description";
video.Snippet.Tags = new string[] { "tag1", "tag2" };
video.Snippet.CategoryId = "22"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = "unlisted"; // or "private" or "public"
var filePath = #"REPLACE_ME.mp4"; // Replace with path to actual movie file.
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
await videosInsertRequest.UploadAsync();
}
}
void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Uploading:
Console.WriteLine("{0} bytes sent.", progress.BytesSent);
break;
case UploadStatus.Failed:
Console.WriteLine("An error prevented the upload from completing.\n{0}", progress.Exception);
break;
}
}
void videosInsertRequest_ResponseReceived(Video video)
{
Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
}
}
}

Resources