How to assign file-name to Azure DocumentDB attachment? - azure

I am uploading an image to documentDb. I assign filename to slug property. I retrieve the attachement using media link obtained during upload.
My expectation is that when I retrieve attachment I would get my filename back. But in contrary Azure returned slug as null. I have double checked content of the retrieved attachement is correct.
Two questions:
Am I misusing slug property?
Where should I assign custom properties?
Here is my Code:
public async Task<string> UploadAttachment(string selfLinkId, Stream mediaStream, string contentType, string fileName)
{
Document foundDocument = (await _client.ReadDocumentAsync(selfLinkId)).Resource;
ResourceResponse<Attachment> resp = await _client.CreateAttachmentAsync(foundDocument.AttachmentsLink, mediaStream, new MediaOptions { ContentType = contentType, Slug = fileName, });
string mediaLink = resp.Resource.MediaLink;
return mediaLink;
}
public async Task<MediaResponse> GetAttachment(string mediaLink)
{
//Use DocumentClient to read the Media content
MediaResponse content = await _client.ReadMediaAsync(mediaLink);
return content;
}

Are you sure the content type is being set correctly?
As per this documentation, slug and contentype are needed only when uploading raw media, which is your scenario. Also, have you checked the Documents Explorer in the azure portal to see how the document and its attachments are stored? Are the values shown there the ones you are expecting?
As per the same documentation, this is an example of how a POST of raw media should look like:
POST https://contosomarketing.documents.azure.com/dbs/hUwBcw==/colls/hUwBc+gfDX4=/docs/hUwBc+gfDX4DAAAAAAAAAA==/attachments HTTP/1.1
x-ms-date: Thu, 14 Aug 2014 22:40:25 GMT
authorization: type%3dmaster%26ver%3d1.0%26sig%3dza46lCo9nNr0%2fGMjryG8S%2b26ZsFABUYPlW3ebq26nDg%3d
x-ms-version: 2014-08-21
Content-Type: image/png
Slug: brush.png
Content-Length: 244

Related

ReadDocumentAsync always fails claiming Id does not exist

I am querying Azure DocumentDb (cosmos) for a document that is present in the container:
try{
doc = await client.ReadDocumentAsync(
GetDocumentUri("tenantString-campaignId"),
new RequestOptions
{
PartitionKey = new PartitionKey(tenantString)
});
}
catch(Exception e)
{
Console.WriteLine(e);
}
for this document:
tenantString-campaignId is the id you can see here and tenantString alone is the partition key this is under. The tenant itself was passed in as a string and I had that working but now I have changed to passing a Tenant object and parsing the string required from it I am not returning the document.
I have tried a few different variations of tenantString and id and I can generate either a DocumentClientException, Id does not exist, Exception or it fails silently; no exception and returns to the calling method where it causes a NullReferenceException as no document is returned.
As far as I can make out from debugging through this I am constructing all my data correctly and yet no document is returned. Does anyone have any idea what I can try next?
This syntax for the .NET SDK v2 is not correct. ReadDocumentAsync() should look like this.
var response = await client.ReadDocumentAsync(
UriFactory.CreateDocumentUri(databaseName, collectionName, "SalesOrder1"),
new RequestOptions { PartitionKey = new PartitionKey("Account1") });
You can see more v2 samples here

Docusign REST API: Downloading document to string

I am building an app using the docusign API and PHP. I have most of this working except I cannot figure out how to download the document. I have been searching on this site and on the Docusign site. Docusign has an example here, that shows how to get a list of docs in PHP, but the downloading does not have a PHP example. In the Docusign REST API docs they explain the method here. But this says the response is "PDF File".
In my sample code below, I have tried to put the contents into a file, but it creates and empty file. If I print_r($data), I get this:
SplFileObject Object
(
[pathName:SplFileInfo:private] => /tmp/419ULk
[fileName:SplFileInfo:private] => 419ULk
[openMode:SplFileObject:private] => w
[delimiter:SplFileObject:private] => ,
[enclosure:SplFileObject:private] => "
)
It does create the file in /tmp, but I want to keep the document in a string so I send or save to DB.
Here is my controller function:
public function get_document($envelopeId, $cert = FALSE)
{
$save_dir = BASEPATH."../documents/";
if ($envelopeId) {
$this->load->model('docusign_model');
$data = $this->docusign_model->get_document($envelopeId, $cert);
}
file_put_contents($save_dir.$envelopeId.".pdf", $data);
//print_r($data);
die("116");
}
This is in docusign_model:
public function get_document($envelopeId, $cert)
{
$docuSignAuth = $this->auth();
if ($docuSignAuth) {
$envelopeApi = new EnvelopesApi($docuSignAuth->apiClient);
$options = new GetDocumentOptions();
if($cert) {
$options->setCertificate(TRUE);
} else {
$options->setCertificate(FALSE);
}
return $envelopeApi->getDocument($docuSignAuth->accountId, 1, $envelopeId, $options);
}
return false;
}
How can I get this document and keep it in a string?
Any and all help is greatly appreciated!
The content comes back as a file, you have to read the temp file and save that to the desired file
Quick snippet using file_get_contents and file_put_contents
$docStream = $envelopeApi->getDocument($accountId, 1, $envelopeId);
file_put_contents("my_document.pdf", file_get_contents($docStream->getPathname()));
More info DocuSign REST API :: EnvelopeDocuments: get under Get a Single Document as a PDF File

Paging in MS Graph API

Graph API Paging explains that the response would contain a field #odata.nextLink which would contain a skiptoken pointing to the next page of contents.
When I test the API, I'm getting a fully-qualified MS Graph URL which contains the skiptoken as a query param. E.g. Below is the value I got for the field #odata.nextLink in the response JSON.
https://graph.microsoft.com/v1.0/users?$top=25&$skiptoken=X%27445370740200001E3A757365723134406F33363561702E6F6E6D6963726F736F66742E636F6D29557365725F31363064343831382D343162382D343961372D383063642D653136636561303437343437001E3A7573657235407368616C696E692D746573742E31626F74322E696E666F29557365725F62666639356437612D333764632D343266652D386335632D373639616534303233396166B900000000000000000000%27
Is it safe to assume we'll always get the full URL and not just the skiptoken? Because if it's true, it helps avoid parsing the skiptoken and then concatenating it to the existing URL to form the full URL ourselves.
EDIT - Compared to MS Graph API, response obtained from Azure AD Graph API differs in that the JSON field #odata.nextLink contains only the skipToken and not the fully-qualified URL.
if you would like to have all users in single list, you can achieve that using the code that follows:
public static async Task<IEnumerable<User>> GetUsersAsync()
{
var graphClient = GetAuthenticatedClient();
List<User> allUsers = new List<User>();
var users = await graphClient.Users.Request().Top(998)
.Select("displayName,mail,givenName,surname,id")
.GetAsync();
while (users.Count > 0)
{
allUsers.AddRange(users);
if (users.NextPageRequest != null)
{
users = await users.NextPageRequest
.GetAsync();
}
else
{
break;
}
}
return allUsers;
}
I am using graph client library
Yes. In Microsoft Graph you can assume that you'll always get the fully qualified URL for the #odata.nextLink. You can simply use the next link to get the next page of results, and clients should treat the nextLink as opaque (which is described in both OData v4 and in the Microsoft REST API guidelines here: https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#98-pagination.
This is different from AAD Graph API (which is not OData v4), which doesn't return the fully qualified next link, and means you need to do some more complicated manipulations to get the next page of results.
Hence Microsoft Graph should make this simpler for you.
Hope this helps,
The above code did not work for me without adding a call to 'CurrentPage' on the last line.
Sample taken from here.
var driveItems = new List<DriveItem>();
var driveItemsPage = await graphClient.Me.Drive.Root.Children.Request().GetAsync();
driveItems.AddRange(driveItemsPage.CurrentPage);
while (driveItemsPage.NextPageRequest != null)
{
driveItemsPage = await driveItemsPage.NextPageRequest.GetAsync();
driveItems.AddRange(driveItemsPage.CurrentPage);
}
I followed Tracy's answer and I was able to fetch all the messages at one go.
public List<Message> GetMessages()
{
var messages = new List<Message>();
var pages = Client.Users[_email]
.Messages
.Request(QueryOptions)
// Fetch the emails with attachments directly instead of downloading them later.
.Expand("attachments")
.GetAsync()
.Result;
messages.AddRange(pages.CurrentPage);
while (pages.NextPageRequest != null)
{
pages = pages.NextPageRequest.GetAsync().Result;
messages.AddRange(pages.CurrentPage);
}
return messages;
}

ServiceStack AutoQuery, Implicit/Explicit Queries

I have the following Request DTO:
[Route("/processresults")]
public class FindProcessResults : QueryBase<ProcessResult, ProcessResultDto> {}
ProcessResult has a property named Id (Int32). I have two ProcessResults in my database, Id 1 and 2.
When I perform a GET to /processresults?Id=1 I get a single ProcessResult returned. Great.
However when I POST this JSON I get two ProcessResults returned. The query is not executing. When I add the property Id to FindProcessResults the JSON call does work, however I have not set EnableUntypedQueries to false.
PostData: {"Id":"1"}
What could be the issue here?
Bonus points, if I make a POST with Form Data I get the following exception:
{
ResponseStatus: {
ErrorCode: "RequestBindingException",
Message: "Unable to bind request",
StackTrace: " at ServiceStack.Host.RestHandler.CreateRequest(IRequest httpReq, IRestPath restPath)\ \ at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest httpReq, IResponse httpRes, String operationName)"
}
}
However if I do the same (a post) with x-www-form-urlencoded the query works as intended (returns a single result).
Conclusion: Whilst I can resolve this issue by adding the parameter I wish to query by (Id) to the typed request, this defeats the purpose of what I am trying to achieve, a generic query mechanism for my data store. The functionality already exists for the GET version of the request.
I believe it is to do with the implementation of AutoQueryServiceBase:
public virtual object Exec<From>(IQuery<From> dto)
{
SqlExpression<From> q;
using (Profiler.Current.Step("AutoQuery.CreateQuery"))
{
q = AutoQuery.CreateQuery(dto, Request.GetRequestParams());
}
using (Profiler.Current.Step("AutoQuery.Execute"))
{
return AutoQuery.Execute(dto, q);
}
}
This is using Request.GetRequestParams() which will return parameters from the Query String or the Form Parameters, whilst a JSON request is attempting to be deserialized into <From> dto. The From type FindProcessResults has no Id property and so it is not populated and passed to the Query.
Requested HTTP Request/Response:
Request
POST /processresults HTTP/1.1
Host: localocl
Accept: application/json
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 36d4b37e-0407-a9b3-f2f2-5b024d7faf7f
{"Id":1}
Response
Cache-Control → private
Content-Length → 1580
Content-Type → application/json; charset=utf-8
Date → Mon, 03 Nov 2014 21:20:43 GMT
Server → Microsoft-IIS/8.5
Vary → Accept
X-AspNet-Version → 4.0.30319
X-Powered-By → ServiceStack/4.033 Win32NT/.NET, ASP.NET
{"Offset":0,"Total":2,"Results"....
You should strongly consider using GET requests for consuming AutoQuery services which is more appropriate HTTP Verb to use, that's also more cacheable and introspectable.
If you want to POST and you don't want to use a HTML Form POST (i.e. x-www-form-urlencoded Content-Type) you will need to formalize the parameters by adding them to the Request DTO:
[Route("/processresults")]
public class FindProcessResults : QueryBase<ProcessResult, ProcessResultDto>
{
public int Id { get; set; }
}
Otherwise it will try to deserialize the JSON into an empty DTO where any non-existing properties are ignored.

Can anyone give me example how to create new IBM Connections Activity using xPages Social Enabler?

Can anyone give me an example how to create new IBM Connections Activity using xPages Social Enabler? I cant find any usefull info in documentation so I have adapted an example from Niklas Heidloff on how to create a new bookmark in Connections. I have the following code for creating a new activity:
try {
var svc = new sbt.ConnectionsService("/activities/service/atom2/activities");
var sb = new java.lang.StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<entry xmlns:snx=\"http://www.ibm.com/xmlns/prod/sn\" xmlns:opensearch=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:thr=\"http://purl.org/syndication/thread/1.0\" xmlns=\"http://www.w3.org/2005/Atom\">");
sb.append("<title type=\"text\">");
sb.append("test activity from xpages");
sb.append("</title>");
sb.append("<content type=\"html\">");
sb.append("</content>");
sb.append("</entry>");
var msg = svc.post(null, sb.toString(), "xml");
} catch(e) {
print(e)
}
But code above do not create anything but raises error on Domino console. This is returned by svc.post() command:
[31726:00075-3041917840] 11/19/2012 01:03:59 PM HTTP JVM: Client service request to: http://vhost1279.site1.compute.ihost.com:81/activities/service/atom2/activities did not return OK status. Status returned: 415, reason: Unsupported Media Type, expected:information, please consult error-l
[31726:00075-3041917840] 11/19/2012 01:03:59 PM HTTP JVM: g-0.xml located in /local/opt/ibm/lotus/notesdata/domino/workspace/logs
[31726:00075-3041917840] 11/19/2012 01:03:59 PM HTTP JVM: com.ibm.xsp.extlib.sbt.services.client.ClientServicesException: HTTP Status 415, Unsupported Media Type. HTTP error response code received in response to request to url: http://vhost1279.site1.comties/service/atom2/activities
Can anyone give me a hint how to use it properly or point me to some usefull documentation?
Don't use a StringBuilder to create XML. At least use SAX or better Apache Abdera for creating XML (Tutorial here). This ensures that your XML is valid and in case of Abdera also valid ATOM.
It is important to use this approach, since you get a Node object in return, that automatically triggers the needed content type.
Then check how to create an Activity in the Connections documentation wiki (yes - confusing). In this article you find the code to retrieve an activity - I actually recommend to use CURL to get the valid format as an example. Some CURL URLs are here. The closest you get to a full example is Luis' demo of a status update.
To explore connections I use the following batch file:
set server=[server]
set HOME=c:\work
curl %server%%1 –-netrc -G --basic -k -v -L -o %2 %3 %4 %5 %6 %7
with an .netrc file (see the CURL documentation)
machine [server] login [user] password [password]
This is the XML format you need for an activity:
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
<category scheme="http://www.ibm.com/xmlns/prod/sn/type" term="activity" label="Activity"/>
<title type="text">Posted activity</title>
<content type="html">
This is an activity that has been automatically uploaded from the cURL command line
</content>
</entry>
And post it like this:
post activities/service/atom2/activities newactivity.xml activityresult.xml
Open the activityresult.xml and locate ocate the href attribute of the app:collection element - you need it to add actions. Use the following XML:
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:snx="http://www.ibm.com/xmlns/prod/sn">
<category scheme="http://www.ibm.com/xmlns/prod/sn/type" term="todo"/>
<category term="Connection4.0"/>
<category term="Test"/>
<title type="text">Some things that need to be done</title>
<content type="html">
This is an <b>action</b> in an activity that has been automatically uploaded from the cURL command line.
</content>
<snx:assignedto>noreply#ibm.com</snx:assignedto>
</entry>
and this command:
post [the-url-you-found-above] newaction.xml actionresult.xml
Once the CURL version works you can try yourself using Abdera code.
Here is a working sample from REST client in Firefox:
https://vhost1279.site1.compute.ihost.com/activities/service/atom2/activities
Header: Content-Type application/atom+xml
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns:snx="http://www.ibm.com/xmlns/prod/sn" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns="http://www.w3.org/2005/Atom">
<category scheme="http://www.ibm.com/xmlns/prod/sn/type" term="activity" label="Activity" />
<content type="html"/>
<title type="text">
test
</title>
</entry>
The problem with the code above is that you pass in a String to the post method. That however doesn't set the right content type. Please use APIs Stephan suggests to create a org.w3c.dom.Node with the XML and pass this in instead. This will set automatically the right content type in the header.
SOLVED!!! I looked to source and problem was obvious. This is rather a bug or at least misconception but can be easily resolved . According the docs and my testing proves this Connections requires the following header in request: Content-Type = application/atom+xml .... but in source code of Social Enabler I found these two related methods:
protected void prepareRequest(HttpClient httpClient, HttpRequestBase httpRequestBase, Options options) throws ClientServicesException {
// TODO: add support for gzip content
//httpClient.addRequestHeader("Accept-Encoding", "gzip");
if(options.getHeaders()!=null) {
addHeaders(httpClient, httpRequestBase, options);
}
if (options.content != null) {
String contentType = null;
HttpEntity entity = null;
Object content = options.content;
try {
//If a subclass overrides com.ibm.xsp.extlib.services.client.Service.processRequestContent(HttpRequestBase, Object, Options)
//the the subclass must set the content type of the request, and also set the request's entity!
if(processRequestContent(httpClient, httpRequestBase, options)){
if (content instanceof IValue) {
JsonFactory jsFactory = new JsonJavaScriptFactory(DesignerRuntime.getJSContext());
entity = new StringEntity(JsonGenerator.toJson(jsFactory, content, true));
contentType = "application/json";
}
else if (content instanceof JsonObject) {
JsonFactory jsFactory = JsonJavaFactory.instanceEx;
entity = new StringEntity(JsonGenerator.toJson(jsFactory, content, true));
contentType = "application/json";
}
else if (content instanceof Node) {
entity = new StringEntity(DOMUtil.getXMLString((Node) content, true));
contentType = "application/xml";
}
else {
entity = new StringEntity(content.toString());
contentType = findRequestTextContentType(options);
}
}
} catch (Exception ex) {
if(ex instanceof ClientServicesException) {
throw (ClientServicesException)ex;
}
throw new ClientServicesException(ex, "Error while parsing request content");
}
if (entity != null && (httpRequestBase instanceof HttpEntityEnclosingRequestBase)) {
httpRequestBase.setHeader("Content-type", contentType);
((HttpEntityEnclosingRequestBase) httpRequestBase).setEntity(entity);
}
}
}
protected String findRequestTextContentType(Options options) {
return "text/plain";
}
As you can see there is no such header (application/atom+xml) for any case. But if you provide XML content as string, the code uses the 'findRequestTextContentType' method to return a default content type, which is 'text/plain' that is not correct for our situation. It is hardcoded so there is no way how to setup the default encoding. But, at least, the 'findRequestTextContentType' is type protected so it can be overriden. So I have created my own ConnectionsService class that extends the former one and overrides the findRequestTextContentType method to return correct content type for my case. And this works fine and resolved the problem !!
import sbt.ConnectionsService;
public class ConnectionsServiceCustom extends ConnectionsService {
public ConnectionsServiceTcl(String serviceUrl) {
super(serviceUrl);
// TODO Auto-generated constructor stub
}
#Override
protected String findRequestTextContentType(Options options) {
return "application/atom+xml";
}
}
As Niklas and Stephen point out you need to use a Dom object (Node, Document etc).. If you are getting an error when creating such an object then it is most likely because the contents of the document/node is poorly formatted or incorrect..
There is a built in XPages util class that allows you to create documents from strings
com.ibm.commons.xml.DOMUtil
Check out
com.ibm.commons.xml.DOMUtil.createDocument(String, String)
e.g.
com.ibm.commons.xml.DOMUtil.createDocument("my xml string", null);
The first parameter is the contents of the XML document, the second is the format.
This class provides several utility methods for parsing and constructing DOM documents.

Resources