I'm trying to copy and move objects between buckets in Google Cloud Storage using the .NET API. As far as I can tell I am constructing the request correctly and I have verified that all the properties I am setting below are correct and what they should be but I get the following cryptic error:
Google.GoogleApiException : Google.Apis.Requests.RequestError
Required [400]
Errors [
Message[Required] Location[ - ] Reason[required] Domain[global]
]
here is the code
Google.Apis.Storage.v1.Data.Object moveObj = new Google.Apis.Storage.v1.Data.Object() { Name = key, Size = (ulong)Length, ContentType = contentType };
ObjectsResource.RewriteRequest req = new ObjectsResource.RewriteRequest(_gcsClient, newObj,sourceBucket, key, destBucket, key);
req.Execute();
Any idea what I might be doing wrong?
I figured it out on my own, hopefully this answer will help you if you're having the same issue. Credit this the poorly written and inconsistent Cloud Storage API more than anything. Every other operation when you create a Storage 'Object' you specify the name for it (see first param):
new Google.Apis.Storage.v1.Data.Object() { Name = key, Size = (ulong)Length, ContentType = contentType };
But for some reason this breaks when trying to move or copy the object so in this case you can omit it as you are passing the destinationKey as a property to the WriteRequest/CopyRequest anyway, so like this:
new Google.Apis.Storage.v1.Data.Object() {Size = (ulong)Length, ContentType = contentType };
Related
What's I'm doing well
I'm currently creating an dotnet core app to consume and process data from an Excel sheet stored in personal OneDrive. I'm using MSAL to create a session token and the data consumption is working great. Here's my working code:
// Get the range for data to process
var dataRangeRequest = myGraphServiceClient // an instance of GraphServiceClient
.Me.Drive.Items[fileItemId]
.Workbook
.Sheets[sheetId]
.UsedRange(valuesOnly: false)
.Request();
var dataRange = await dataRangeRequest.GetAsync(ct)
// Extract column names (headers) from the data range
var columnNames = dataRange.Text.First.ToObject<string[]>();
// Extract data cells from the data range
var lines = dataRange.Text.Skip(1).Select(line=>line.ToObject<string[]>).ToArray();
[...] // Here I process the lines using the columnNames.
// --EVERYTHING WORKS UNTIL HERE--
What I'm not doing well
Now, I want to turn red a faulty data cell in the original Excel document
var faultyCell = (row: 34, column: 5); // the row/column offset of the faulty cell in dataRange
// ---------------------------------
// --FOLLOWING CODE IS NOT WORKING--
// ---------------------------------
var changeRange = new WorkbookRange
{
RowIndex = faultyCell.row,
ColumnIndex = faultyCell.column,
RowCount = 1,
ColumnCount = 1,
Format = new WorkbookRangeFormat {Fill = new WorkbookRangeFill {Color = "red"}}
};
await dataRangeRequest.PatchAsync(changeRange, ct); // Throwing a Microsoft.Graph.ServiceException
I intercepted the HTTP request & response and it's the following:
REQUEST
PATCH https://graph.microsoft.com/v1.0/me/drive/items/<file id>/workbook/worksheets/{sheetId}/microsoft.graph.usedRange(valuesOnly=true) HTTP/1.1
{
"columnIndex": 5,
"rowIndex": 34,
"columnCount": 1,
"rowCount": 1,
"format": {
"fill": {
"color": "Red",
"#odata.type": "microsoft.graph.workbookRangeFill"
},
"#odata.type": "microsoft.graph.workbookRangeFormat"
},
"#odata.type": "microsoft.graph.workbookRange"
}
RESPONSE
400 Bad Request
{
"error": {
"code": "BadRequest",
"message": "Bad Request - Error in query syntax.",
"innerError": {
"date": "<the date>",
"request-id": "<a guid>",
"client-request-id": "<another guid>"
}
}
}
Success with a manual HTTP request
I succeed to update manually the cell using a HTTP request by following the documentation.
WORKING REQUEST:
PATCH https://graph.microsoft.com/v1.0/me/drive/items/<file id>/workbook/worksheets/{sheetId}/range(address='F35:F35')/format/fill
{"color": "red"}
Problems
I don't know how to generate this HTTP request from C# by using the Microsoft.Graph api. (the documentation is obsoleted, there's no .Format on IWorkbookWorksheetRangeRequestBuilder. This error seems documented on GitHub. Is there an easy way to use the graph SDK to send an arbritary http request?
More importantly: for this to work, I need to translate the cell offset to a range address. Is there an utility somewhere to do that? In my example I manually translated the offset 5,34 in the range to address F35.
Specifications
Packages:
Microsoft.Graph: v3.15.0 (latest release version)
Microsoft.Identity.Client: (MSAL) v4.15.0 (not the latest version, but shoudn't be a problem here)
I can confirm, that this issue with no .Format on IWorkbookWorksheetRangeRequestBuilder is frustrating for me as well ;) I've reported this issue on GitHub:
https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/233
For me the workaround was to do it like so (i was basing it on this SOF: REST call to Microsoft Graph
var requestUrl = $#"https://graph.microsoft.com/v1.0/users/{user id}/drive/items/{Consts.DriveId}/workbook/worksheets/{Consts.SheetId}/range(address='{range}')/{resource}";
string workbookRangeFill = GraphServiceClient.HttpProvider.Serializer.SerializeObject(workbookRange);
// Create the request message and add the content.
HttpRequestMessage hrm = new HttpRequestMessage(new HttpMethod(httpMethod), requestUrl);
hrm.Content = new StringContent(workbookRangeFill, System.Text.Encoding.UTF8, "application/json");
// Authenticate (add access token) our HttpRequestMessage
await GraphServiceClient.AuthenticationProvider.AuthenticateRequestAsync(hrm);
// Send the request and get the response.
HttpResponseMessage response = await GraphServiceClient.HttpProvider.SendAsync(hrm);
Whereas workbookRange variable is of either of type: WorkbookRangeFill or WorkbookRangeFont (Depending on the need) I assume you will be interested in WorkbookRangeFill (to change the color in range/cell)
range variable is range in spreadsheet in the format: "A1:B3"
resource variable is format/fill for WorkbookRangeFill and format/font for WorkbookRangeFont
and of course {user id} is the user which owns the document (i am using client credentials flow with with application permisions For other scenarios i assume you can just change one thing in the code above. So that, instead of:
https://graph.microsoft.com/v1.0/users/{user id}/drive
to use
https://graph.microsoft.com/v1.0/me/drive
I am trying to broadcast live from my .Net application using Youtube.Data.Api v3.
I have set up OAuth and downloaded the .JSON file, and that works fine. I know that, because I have already successfully obtained a list of channels resp. videos on my account, i.e., following code works:
var channelsRequest = ytService.Channels.List("contentDetails, snippet");
channelsRequest.Mine = true;
var channelsListResponse = channelsRequest.Execute();
But if I try to execute a insert request (for completeness I show you the whole method),
public static LiveBroadcast CreateImmediateBroadcast(string title = "DefaultBroadcast") {
var snippet = new LiveBroadcastSnippet();
snippet.Title = title;
snippet.ScheduledStartTime = DateTime.Now;
snippet.ScheduledEndTime = DateTime.Now + TimeSpan.FromMinutes(60);
var status = new LiveBroadcastStatus();
status.PrivacyStatus = "unlisted";
var broadcast = new LiveBroadcast();
broadcast.Kind = "youtube#liveBroadcast";
broadcast.Snippet = snippet;
broadcast.Status = status;
var insertBroadcastRequest = ytService.LiveBroadcasts.Insert(broadcast, "snippet, status");
insertBroadcastRequest.Execute();
return broadcast;
}
I get an exception when calling insertBroadcastRequest.Execute(), namely:
Google.GoogleApiException was unhandled
HResult=-2146233088
Message=Google.Apis.Requests.RequestError
Insufficient Permission [403]
Errors [
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermissions] Domain[global]
]
ServiceName=youtube
Source=Google.Apis
StackTrace:
at Google.Apis.Requests.ClientServiceRequest`1.Execute() in C:\Users\cloudsharp\Documents\GitHub\google-api-dotnet-client\Src\Support\GoogleApis\Apis\Requests\ClientServiceRequest.cs:line 96
at YoutubeConsole.YouTubeAPI.CreateImmediateStream(String title) in C:\Users\bussg\Source\Workspaces\OwnExperimental\YoutubeConsole\YoutubeConsole\YouTubeAPI.cs:line 87
at YoutubeConsole.YouTubeAPI.Test() in
...
Also, for completeness, here is my authorization,
using (var stream = new FileStream(Directory.GetCurrentDirectory() + #"\GoogleAuthOtherApplication.json", FileMode.Open, FileAccess.Read)) {
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeForceSsl},
"user",
CancellationToken.None,
new FileDataStore("YouTubeAPI")
).Result;
}
Also, For the YouTubeService.Scope I have tried all options. The insert method should work with ForceSsl according to the documentation.
Also this documentation page sais
Note: A channel must be approved to use the YouTube Live feature, which enables the channel owner to stream live content to that channel. If you send API requests on behalf of an authenticated user whose channel is not enabled or eligible to stream live content, the API will return an insufficientPermissions error.
But all my channels are approved for Youtube Live. Any ideas how to get this to work?
Ok after some testing between us over Email.
You need to have the correct scope "YouTubeService.Scope.YoutubeForceSsl" by changing "user" we forced it to request permissions again. My tutorial on how filedata store works in the Google .net client library
remove the space "snippet, status" by sending "snippet,status" it worked for me.
For the fun of it: Issue 8568:LiveBroadcasts: insert - spaces in part
How can I add a new document to Content Server 10.5 using the REST api?
I am following the Swagger docs for creating a node, but it is not clear how I attach the file to the request. Here is (roughly) the code I am using:
var folderId = 2000;
var docName = "test";
var uri = $"http://[serverName]/otcs/llisapi.dll/api/v1/nodes?type=144&parent_id={folderId}&name={docName}";
var request = new HttpRequestMessage();
request.Headers.Add("Connection", new[] { "Keep-Alive" });
request.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
request.Headers.Add("Pragma", "no-cache");
request.Headers.Add("OTCSTicket", /* ticket here */);
request.RequestUri = new Uri(uri);
request.Method = HttpMethod.Post;
request.Content = new ByteArrayContent(data);
request.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(filePath));
request.Headers.ExpectContinue = false;
var httpClientHandler = new HttpClientHandler
{
Proxy = WebRequest.GetSystemWebProxy(),
UseProxy = true,
AllowAutoRedirect = true
};
using (var client = new HttpClient(httpClientHandler))
{
var response = client.SendAsync(request).Result;
IEnumerable<string> temp;
var vals = response.Headers.TryGetValues("OTCSTicket", out temp) ? temp : new List<string>();
if (vals.Any())
{
this.ticket = vals.First();
}
return response.Content.ReadAsStringAsync().Result;
}
I've been searching through the developer.opentext.com forums, but finding a complete example in c# is proving tough - there are a few examples in javascript, but attempting to replicate these in c# or via chrome or firefox extensions just give the same results. Calling other CS REST methods has not been an issue so far, this is the first one that's giving me problems.
Edit: I pasted the wrong url into my question, which I've now fixed. It was var uri = $"http://[serverName]/otcs/llisapi.dll/api/v1/forms/nodes/create?type=0&parent_id={folderId}&name={docName}";.
Your URL doesn't look like the REST API, it's rather the traditional URL used for the UI.
This article should describe how to do what you want to do:
https://developer.opentext.com/webaccess/#url=%2Fawd%2Fresources%2Farticles%2F6102%2Fcontent%2Bserver%2Brest%2Bapi%2B%2Bquick%2Bstart%2Bguide&tab=501
EDITED:
Ok, so that's how it should work:
send a POST to http://www.your_content_server.com/cs[.exe]/api/v1/nodes
send this in your payload to create a document in your enterprise workspace
type=144
parent_id=2000
name=document_name.txt
<file>
A incomplete demo in Python would look like this. Make sure you get a valid ticket first.
files = {'file': (open("file.txt", 'rb')}
data = { 'type': 144, 'parent_id': 2000, 'name': 'document_name.txt' }
cs = requests.post(url, headers={'otcsticket':'xxxxxxx'}, data=data, files=files)
if cs.status_code == 200:
print "ok"
else:
print cs.text
You will need a form input to get the file onto the page then you can use filestreams to redirect it, there is great guide for that here.
Reading files in JavaScript using the File APIs
Here is a Jquery/ Ajax example.
I find the best way to go about this is to use Postman (Chrome Plugin) to experiment until you get comfortable.
var form = new FormData();
form.append("file", "*filestream*");
form.append("parent_id", "100000");
form.append("name", "NameYourCreatedFile");
form.append("type", "144");
var settings = {
"async": true,
"url": "/cs.exe/api/v1/nodes", // You will need to amend this to match your environment
"method": "POST",
"headers": {
"authorization": "Basic **use Postman to generate this**",
"cache-control": "no-cache",
},
"processData": false,
"contentType": false,
"mimeType": "multipart/form-data",
"data": form
}
$.ajax(settings).done(function (response) {
console.log(response);
});
It appears that the OpenText API only supports file uploads through asynchronous JavaScript uploads - not through traditional file uploads by using typical posted form requests that contain the files contents (which is pretty crappy to be honest - as this would be the easiest to handle on server side).
I've contacted their support and they were absolutely no help - they said since it's working with JavaScript, then they can't help me. Anyone else utilizing any language besides JavaScript is SOL. I submitted my entire API package, but they didn't bother investigating and wanted to close my ticket ASAP.
The only way I've found to do this, is to upload / send the file into your 'Upload' directory on your Content Servers web server (on ours it was set to D:\Upload).This directory location is configurable in the admin section.
Once you've sent the file to your web server, send a create node request with the file param set to the full file path of the file residing on your server, as the OpenText API will attempt to retrieve the file from this directory.
I've created a PHP API for this, and you can browse its usage here:
https://github.com/FBCLIT/OpenTextApi
<?php
use Fbcl\OpenTextApi\Client;
$client = new Client('http://server.com/otcs/cs.exe', 'v1');
$api = $client->connect('username', 'secret');
try {
// The folder node ID of where the file will be created under.
$parentNodeId = '12356';
// The file name to display in OpenText
$fileName = 'My Document.txt';
// The actual file path of the file on the OpenText server.
$serverFilePath = 'D:\Upload\My Document.txt';
$response = $api->createNodeDocument($parentNodeId, $fileName, $serverFilePath);
if (isset($response['id'])) {
// The ID of the newly created document will be returned.
echo $response['id'];
}
} catch (\Exception $ex) {
// File not found on server drive, or issue creating node from given parent.
}
MIME Type detection appears to happen automatically, and you do not need to send anything for it to detect the file type. You can name the file to whatever you like without an extension.
I have also discovered that you cannot use an IP address or Host name for uploading files in this manor. You must enter a path that is local to the server you are uploading to. You can however give just the file name that exists in the Upload directory, and the OpenText API seems to locate it fine.
For example, you can pass either D:\Uploads\Document.txt or Document.txt.
If you haven't done it correctly, you should get the error:
Client error: POST http://server.com/otcs/cs.exe/api/v1/nodes resulted in a 400 Bad Request response: {"error":"Error: File could not be found within the upload directory."}
Basically, I have a custom object inheriting from place. I am creating a c# tool for creating the objects using the Facebook Sharp c# SDK located here I am using an app access token when making these calls.
I've tried various approaches and variation within:
Here is a sample call that yeilds:
(OAuthException - #100) (#100) The parameter object is required
_api.AccessToken = GetExtendedToken().Token;
var postdata = new Dictionary<string, object>
{
//{"fb:app_id", "appId"},
//{"type", "myapp:myobject"},
{"url", resort.Url},
{"title", resort.Name },
{"image", resort.Image},
{"video", resort.Video},
{"description", resort.Description},
{"place:location:latitude", lat},
{"place:location:longitude", long}
};
var response = _api.Post("/app/objects/myapp:myobject", postdata);
if I uncomment the type parameter I get:
(OAuthException - #2500) Cannot specify type in both the path and query parameter
If I add the type back in, and remove the type from the path, I get
a response of true but this should be something like id:
23049820398092384
If I remove the place objects, or if I remove place and type, or if
I remove place but use the type and change the get path, I still get
errors.
refactored a bit. In this scenario I needed to assign the postdata information to an object as such below. also needed to stop using facebooksharp, their api does weird stuff to the request.
var obj = new
{
app_id = appId,
type = app_name:object_type",
url = Url,
title = Name,
image = Image,
video = Video,
description = Description
};
var vars = new NameValueCollection {{"object", JsonConvert.SerializeObject(obj)}, {"format", "json"}, {"access_token", AppToken}};
var url = "https://graph.facebook.com/app/objects/"+ _appName + object_type;
return HttpTools.Post(url, vars);
I am trying to access the file that has been uploaded to Amazon S3 by using the method GetPreSignedUrlRequest. The code I am using is as below:-
string bucketName = string.Empty;
if (ConfigurationManager.AppSettings["S3BucketName"] != null)
{
bucketName = ConfigurationManager.AppSettings["S3BucketName"].ToString();
}
AmazonS3Client s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest
{
BucketName = bucketName,
Key = file.FileName,
Expires = DateTime.Now.AddMinutes(5),
Protocol = Protocol.HTTP
};
string url = s3Client.GetPreSignedURL(request);
The url generated by this is then used to point to the file.
It looks like
http://s3.amazonaws.com/mybucketname/VZcbKsZgR2qyOMkLU1XT_jquery_ui_touch-punch_min_js.txt?X-Amz-Expires=300&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMGQJ6D5L5SNBGHA/20140114/us-east-1/s3/aws4_request&X-Amz-Date=20140114T194159Z&X-Amz-SignedHeaders=host&X-Amz-Signature=922719dd2286600aebaca5701a8e142d327342b541569c9a4d7d8afc822d9a76/VZcbKsZgR2qyOMkLU1XT_jquery_ui_touch-punch_min_js.txt
But that gives me signature doesnot match error as shown in the image below:-
Update - version 2.0.6 of the SDK, released Jan 16th, contains a fix for this issue.
Your code is fine, unfortunately a bug in the SDK is causing the presigned url to be malformed. I've just tested it with our latest codebase and we've fixed the issue; this new version should be released soon.
I'll ping this issue once we release the patch. Sorry for the inconvenience.
Regards,
This can also occur if some of the details about the request are not set up properly:
e.g. For me the following lines fixed the problem
request1.ContentType = "image/jpeg";
request1.Verb = HttpVerb.PUT;