Unable to upload file using Microsoft Graph SDK - c#

I'm trying to upload a file using Microsoft's Graph SDK but have hit a problem.
I have pretty much copied verbatim the C# example from here, commented-out the progress part and updated the using statement for C# 8, and here's what I have...
public async Task<bool> UploadFileAsync(string parentFolderId, string filename, byte[] bytes)
{
var graphClient = GetGraphClient();
// Declare the variable outside the `using` statement to get around a little C# problem - https://www.tabsoverspaces.com/233779-using-await-using-iasyncdisposable-with-configureawait
var memoryStream = new MemoryStream(bytes);
await using (memoryStream.ConfigureAwait(false))
{
// Use properties to specify the conflict behavior.
// - in this case, replace.
var uploadProps = new DriveItemUploadableProperties
{
AdditionalData = new Dictionary<string, object> {{"#microsoft.graph.conflictBehavior", "replace"}},
ODataType = null
};
try
{
// Create the upload session.
// - itemPath does not need to be a path to an existing item.
var uploadSession = await graphClient.Drives[_driveId]
.Items[parentFolderId]
.ItemWithPath(filename)
.CreateUploadSession(uploadProps)
.Request()
.PostAsync()
.ConfigureAwait(false);
// Max slice size must be a multiple of 320KB.
const int maxSliceSize = 320 * 1024;
var fileUploadTask = new LargeFileUploadTask<DriveItem>(uploadSession, memoryStream, maxSliceSize);
// Upload the file.
var uploadResult = await fileUploadTask.UploadAsync().ConfigureAwait(false);
if (uploadResult.UploadSucceeded)
{
// The ItemResponse object in the result represents the created item.
return true;
}
return false;
}
catch (ServiceException exception)
{
// ...
}
}
}
However the line...
var uploadSession = await graphClient.Drives[_driveId]
.Items[parentFolderId]
...
...throws an exception:
Microsoft.Graph.ServiceException: Code: BadRequest
Message: Multiple action overloads were found with the same binding parameter for 'microsoft.graph.createUploadSession'.
Can anyone help?

I figured out the cause of the problem - the filename that I was using contained invalid symbols (in my case I was stringifying a DateTime and that contained :).
It's frustrating that this exception doesn't bubble-up correctly and instead I got that "Multiple action overloads" message.

Related

Sending photos to Telegram using the SendMediaGroupAsync method c#

I need to send a photo album in a bundle to the telegram bot. The number of photos is unknown in advance.
I wrote the code:
List<IAlbumInputMedia> streamArray = new List<IAlbumInputMedia> {};
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
using var stream = formFile.OpenReadStream();
streamArray.Add(stream); // there is a mistake here. cannot convert to System.IO.Stream to Telegram.Bot.Types.IAlbumInputmedia
//await clientTg.SendPhotoAsync(groupId,stream); // it works fine
}
}
await clientTg.SendMediaGroupAsync(groupId, streamArray);
I can't add stream to List arrayStream, error "cannot convert to System.IO.Stream to Telegram.Bot.Types.IAlbumInputmedia"
In a single instance, the stream is normally sent via the SendPhotoAsync method, commented out in the code.
How do I convert these types and send a group photo?
According to the docs:
Message[] messages = await botClient.SendMediaGroupAsync(
chatId: chatId,
media: new IAlbumInputMedia[]
{
new InputMediaPhoto("https://cdn.pixabay.com/photo/2017/06/20/19/22/fuchs-2424369_640.jpg"),
new InputMediaPhoto("https://cdn.pixabay.com/photo/2017/04/11/21/34/giraffe-2222908_640.jpg"),
}
);
You must explicitly set the type of files.
In your case it will be like:
streamArray.Add(new InputMediaPhoto(stream, $"file{DateTime.Now.ToString("s").Replace(":", ".")}")
#Vadim's answer didn't work, probably because I can't Add in this case. But their answer did push me in the right direction. I decided to write code for a different number of photos of different branches of the program.
if (files.Count == 2) // <<<< 2 photos
{
await using var stream1 = files[0].OpenReadStream();
await using var stream2 = files[1].OpenReadStream();
IAlbumInputMedia[] streamArray =
{
new InputMediaPhoto(new InputMedia(stream1, "111"))
{
Caption = "Cap 111"
},
new InputMediaPhoto(new InputMedia(stream2, "222"))
{
Caption = "Cap 222"
},
};
await clientTg.SendMediaGroupAsync(groupId, streamArray);
}
I'm not sure that I'm using await using correctly, but at least it works.

How to increase performance for converting docx to pdf in Microsoft Graph Api

We are doing a docx-PDF conversion by uploading a Word-document(docx) by first uploading it using the large document upload in Microsoft Graph API (link) and after that downloading it to PDF-format (link). However, the whole process (upload + download) takes around 10-15s (file sizes around 5-15 MB) to complete and we would like to know if there are any possibilities to improve the performance?
Code sample from Microsoft for the file upload:
using (var fileStream = System.IO.File.OpenRead(filePath))
{
// Use properties to specify the conflict behavior
// in this case, replace
var uploadProps = new DriveItemUploadableProperties
{
ODataType = null,
AdditionalData = new Dictionary<string, object>
{
{ "#microsoft.graph.conflictBehavior", "replace" }
}
};
// Create the upload session
// itemPath does not need to be a path to an existing item
var uploadSession = await graphClient.Me.Drive.Root
.ItemWithPath(itemPath)
.CreateUploadSession(uploadProps)
.Request()
.PostAsync();
// Max slice size must be a multiple of 320 KiB
int maxSliceSize = 320 * 1024;
var fileUploadTask =
new LargeFileUploadTask<DriveItem>(uploadSession, fileStream, maxSliceSize);
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(prog => {
Console.WriteLine($"Uploaded {prog} bytes of {fileStream.Length} bytes");
});
try
{
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
// The ItemResponse object in the result represents the
// created item.
Console.WriteLine($"Upload complete, item ID: {uploadResult.ItemResponse.Id}");
}
else
{
Console.WriteLine("Upload failed");
}
}
catch (ServiceException ex)
{
Console.WriteLine($"Error uploading: {ex.ToString()}");
}
}
Here I am unsure what the maxSliceSize should be, the docs merely state that it should be a "Multiple of 320 KiB (320 * 1024)" (ref). Does this affect the performance in any way?
Code sample for download:
private IDriveRequestBuilder GetDrive()
{
var graphServiceClient = new GraphServiceClient(_authenctication)
return graphServiceClient.Sites[_oneDriveOptions.SiteId].Drive;
}
private async Task<Stream> GetPdf(string driveItemId)
{
var pdfContentRequest = GetDrive().Items[driveItemId].Content.Request();
pdfContentRequest.QueryOptions.Add(new QueryOption("format", "pdf"));
var result = await pdfContentRequest.GetAsync();
return result;
}
Is 10-15s a reasonable amount of time to process both requests? I would guess this would be much faster if doing in memory but we haven't found any good enough library for conversion that suits our needs so we are currently stuck with the current approach. Maybe uploading/downloading to Onedrive adds a lot of overhead?

how to correctly read from isolated file in windows phone development

i have this two methods for writting and reading from the file.
public static async Task WriteDataToFileAsync(string fileName, string content)
{
byte[] data = Encoding.Unicode.GetBytes(content);
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists);
using (var s = await file.OpenStreamForWriteAsync())
{
await s.WriteAsync(data, 0, data.Length);
}
}
public async static Task<string> ReadFileContentsAsync()
{
var folder = ApplicationData.Current.LocalFolder;
try
{
var file = await folder.OpenStreamForReadAsync("MenuData.json");
using (var streamReader = new StreamReader(file))
{
Debug.WriteLine(streamReader.ReadToEnd());
return streamReader.ReadToEnd();
}
}
catch (Exception)
{
return string.Empty;
}
}
which are then used in this two methods
public static async void ApiToFileRestaurants()
{
HttpClient client = new HttpClient();
HttpResponseMessage response = client.GetAsync("http://bonar.si/api/restaurants").Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
await Restaurant.WriteDataToFileAsync("MenuData.json", responseBody);
}
public async static Task<List<Restaurant>> FileToRestaurantList()
{
var responseBody = await Restaurant.ReadFileContentsAsync();
List<Restaurant> parsedRestaurants = (List<Restaurant>)Newtonsoft.Json.JsonConvert.DeserializeObject(responseBody, typeof(List<Restaurant>));
return parsedRestaurants;
}
now my problem here is that ReadFileAsync doesn't return the results which i know are saved in MenuData.json file but instead returns empty string.
I was mostly getting source code for this from msdn
documentation.
Location of the file in my wp power tools looks like that.
I'm a novice programer so i might overlooked something else
Can you try to read the data from file asyncronously by using ReadToEndAsync which basically parses the complete data and sends response as one string.
var file = await folder.OpenStreamForReadAsync("MenuData.json");
using (var streamReader = new StreamReader(file))
{
return await streamReader.ReadToEndAsync();
}
Hope this helps!
i got the solution from one other forum
You're calling streamReader.ReadToEnd() twice. The first time you log
it to the Debug stream, the second is what you actually use as a
result. The method moves the file pointer to the end everytime it's
called and by the second time there's nothing to read.
so removing that debug line almost fixed my problem. I did get the string i wanted to but there was an error somewhere in it so Newtonsoft.Json had a hard time parsing it. So i tried #asitis solution and changed .json to .text and it worked

Load object list from file.json

I have a file Add.xaml
Inside, I have my recipe class that store my recipe object inside a list named breakfastRecipe
I then store the list beakfastRecipe inside a breakfast.json file. (using Deserialize/serialize methods i found from another member here)
I have a couple of textboxes where i write the name + ingredient then i use a save button that activate the json serializer. I used test method to test the deserializer if everything was working and it does. on the add.xaml page , the save and load of the object list is working perfectly
I have another page breakfastList.xaml where i want use longlistselector to populate my page with all my recipes. The problem is i don't how to access that breakfast.json file that was created on the other page.
I used the same get/deserializer to load my data
public async void btnGet_Tap()
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
try
{
// Getting JSON from file if it exists, or file not found exception if it does not
StorageFile textFile = await localFolder.GetFileAsync("breakfastList.json");
using (IRandomAccessStream textStream = await textFile.OpenReadAsync())
{
// Read text stream
using (DataReader textReader = new DataReader(textStream))
{
//get size
uint textLength = (uint)textStream.Size;
await textReader.LoadAsync(textLength);
// read it
string jsonContents = textReader.ReadString(textLength);
// deserialize back to our products!
//I only had to change this following line in this function
breakfastRecipe = JsonConvert.DeserializeObject<IList<Recipe>>(jsonContents) as List<Recipe>;
// and show it
//displayProduct();
}
}
}
catch (Exception ex)
{
tester.Text = "error";
}
}
When i write in the constructor
breakfastRecipe = new List();
btnGet_Tap();
In my mind, it i'm creating a new list then the btnGet load the data from that .json file that my add.xaml have created but it isn't working...
my question is then how to access the data on my "second page" which is stored in that .json file which was created by my first page
Try moving your btnGet_Tap code into an async function that returns a task and then await in the btnGet_Tap() method. You can then call
Foo().Wait();
if you wish to wait when calling in your constructor.
public async void btnGet_Tap()
{
await Foo();
}
public async Task Foo()
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
try
{
// Getting JSON from file if it exists, or file not found exception if it does not
StorageFile textFile = await localFolder.GetFileAsync("breakfastList.json");
using (IRandomAccessStream textStream = await textFile.OpenReadAsync())
{
// Read text stream
using (DataReader textReader = new DataReader(textStream))
{
//get size
uint textLength = (uint)textStream.Size;
await textReader.LoadAsync(textLength);
// read it
string jsonContents = textReader.ReadString(textLength);
// deserialize back to our products!
//I only had to change this following line in this function
breakfastRecipe = JsonConvert.DeserializeObject<IList<Recipe>>(jsonContents) as List<Recipe>;
// and show it
//displayProduct();
}
}
}
catch (Exception ex)
{
tester.Text = "error";
}
}

Incomplete Azure download

I'm having a problem with inclomplete blobs being downloaded from Azure storage. The files that are stored are an images. Almost every file that's downloaded ends up missing several lines on the bottom. I've checked the blobs and they were uploaded correctly.
I'm using the following code for downloading a blob from the Azure service:
private async Task Download(CloudBlobClient client)
{
try
{
_media = await _directory.CreateFileAsync(ResourceName, CreationCollisionOption.FailIfExists);
}
catch (Exception)
{
return;
}
using (var stream = await _media.OpenAsync(FileAccessMode.ReadWrite))
{
var blob = await GetBlob(client);
await blob.DownloadToStreamAsync(stream);
_category.NotifyAzureProgress();
await stream.FlushAsync();
}
}
The method GetBlob() looks like this:
private async Task<CloudBlockBlob> GetBlob(CloudBlobClient client)
{
CloudBlobContainer container = client.GetContainerReference(ContainerName);
await container.CreateIfNotExistsAsync();
var blob = container.GetBlockBlobReference(ResourceName);
return blob;
}
Upload code:
private async Task UploadAsync(CloudBlobClient client)
{
_media = await _directory.GetFileAsync(ResourceName);
using (var stream = await _media.OpenAsync(FileAccessMode.Read))
{
var blob = await GetBlob(client);
await blob.UploadFromStreamAsync(stream);
_category.NotifyAzureProgress();
}
}
Thanks for any help!
Edit: I've realized I've missed out one detail - the downloaded image has correct dimensions, but several lines from the bottom are black - it doesn't has the same pixels as the source image. I've checked the MD5 hashes and while they match, when I download the image through an external app, they don't match when I download them with the code above.
Edit2: after inspecting the properties of CloudBlob and the output stream, I've noticed, that even though the blob gives correct length after download, the stream usually says something a little lower. I've tried downloading throught range, but to no avail
Ok, so I've managed to download the images afterall, by partially using the WinRT Azure library combined with a standard .NET HttpClient.
I used the Azure Lib establish the initial connection and then to get only the Blob reference, because the BlockBlobReference has a method to create Shared Access Signature (and I really didn't want to try to construct it myself). Then I created the HttpClient, made a download URL using the SAS and issued a GET request to the URL, which finally worked and downloaded all the images intact.
I think there might be some weird bug in the official library, since using my download method instead of theirs solved everything.
Code sample:
internal async Task Download(CloudBlobClient client)
{
try
{
_media = await _directory.CreateFileAsync(ResourceName, CreationCollisionOption.FailIfExists);
}
catch (Exception)
{
return;
}
try
{
var blob = await GetBlob(client);
HttpClient httpClient = new HttpClient();
var date = DateTime.UtcNow;
var policy = new SharedAccessBlobPolicy();
policy.Permissions = SharedAccessBlobPermissions.Read;
policy.SharedAccessStartTime = new DateTimeOffset(date);
policy.SharedAccessExpiryTime = new DateTimeOffset(date.AddDays(1));
var signature = blob.GetSharedAccessSignature(policy);
var uriString = string.Format("{0}{1}", blob.Uri.ToString(), signature);
var data = await httpClient.GetByteArrayAsync(uriString);
var buf = new Windows.Storage.Streams.Buffer((uint)data.Length);
await FileIO.WriteBytesAsync(_media, data);
_category.NotifyAzureProgress();
}
catch (Exception e)
{
_media.DeleteAsync();
throw e;
}
}

Categories