I have an Entity in Dynamics CRM 2011 where license files are stored in base64-encoded format.
Up until now, the licenses has been delivered by email to the recipient (creating an e-mail activity and adding the file as attachment). I now also want to add the possibility to download the file directly from within Dynamics CRM.
Is there any way for a CRM 2011 Plugin to trigger a download of the file (base64-encoded string) to the client web browser?
I.e. I want the PostLicenseUpdate class / ExecutePostLicenseUpdate function to start/trigger a download of the file.
A very similar problem was solved by creating a separate .aspx on the webserver, however I'd prefer using a built in function i CRM.
Here's my solution to the problem.
Except for jQuery, grab FileSaver.js, Blob.js and base64-binary.js.
Create your own download.js file as below, and add the addRequestDownloadLink-function to the OnLoad event of the form.
As the original license data is binary, you'll need to use Base64-binary.js instead of i.e. atob().
function addRequestDownloadLink() {
$('#my_download').append('<div id="my_download_downloadcontainer">Request download of license file</div>');
}
$('body').on('click', '#my_download_downloadlink', function() {
var str_decoded = Base64Binary.decode($(this).attr('data-license'));
var blob = new Blob([str_decoded], {type: "license/binary"});
saveAs(blob, $(this).attr('data-filename'));
});
$('body').on('click', '#my_download_requestdownload', function() {
var guid = Xrm.Page.data.entity.getId();
guid = guid.replace("{", ""); guid = guid.replace("}", "");
var filename = Xrm.Page.getAttribute("my_name").getValue() + ".dat";
var organization = Xrm.Page.context.getOrgUniqueName();
var entity = "my_license";
var select = "?$select=my_DataSigned"
var oDataSelect = "/" + organization + "/XRMServices/2011/OrganizationData.svc/" + entity + "Set(guid'" + guid + "')" + select;
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
datatype: "json",
url: oDataSelect,
beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); },
success: function (data, textStatus, XmlHttpRequest) {
if (data.d.my_DataSigned != null) {
$('#my_download_downloadcontainer').html('<a href="#" id="my_download_downloadlink" data-license="' + data.d.my_DataSigned + '" data-filename="' + filename + '">Download license file!</span>');
}
else {
alert("No license found!");
}
},
error: function (xmlHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus + "; ErrorThrown: " + errorThrown);
}
});
});
Tadaaa! Hope this will help someone with the same issue.
I don't think its possible using Plugins. You can try JavaScript.
Create a Custom button on Ribbon, and edit to run a javascript function on button click. Retrieve the value using OData call and write into a file. Then trigger the download. Have a look at this.
Note: If you are going to use this approach please be careful because there won't be any ribbons in CRM 2013. So in future, you might have to tweak it.
Related
Say you have a file path like: https://contoso.sharepoint.com/sites/somesite/MyDocLib/Folder/Foo.docx
What's the easiest way to turn this into a Microsoft Graph call to fetch the contents of the file which I assume we need to do via the drives endpoint using the correct id's.
I assume I might have to run multiple calls and perhaps assume that /slash1/slash2 is the site, then the next is the doclib etc(?)
Not sure is it the easiest or the only option but the below solution demonstrates how to
meet the requirements of Addressing resources in OneDrive API:
first step would be to transform the URL into a sharing token (see below section), for that matter we utilize Shares API
once the sharing token is generated, the OneDrive API request to download a file could be constructed like this: /shares/{shareIdOrEncodedSharingUrl}/driveitem/content
How to transform the URL into a sharing token
For url:
https://contoso.sharepoint.com/sites/somesite/MyDocLib/Folder/Foo.docx
should be generated the following token:
u!aHR0cHM6Ly9jb250b3NvLnNoYXJlcG9pbnQuY29tL3NpdGVzL3NvbWVzaXRlL015RG9jTGliL0ZvbGRlci9Gb28uZG9jeA
On how to encode a URL is described in MS Graph documentation (C# version is provided there)
NodeJS version:
function urlToToSharingToken(url) {
var trimEnd = function(str, c) {
c = c ? c : ' ';
var i = str.length - 1;
for (; i >= 0 && str.charAt(i) == c; i--);
return str.substring(0, i + 1);
};
var value = Buffer.from(url).toString('base64');
return "u!" + trimEnd(value, '=').replace(/\//g, '_').replace(/\+/g, '-');
}
Example
The example demonstrates how download a file by url provided in the https://contoso.sharepoint.com/sites/somesite/MyDocLib/Folder/Foo.docx format using msgraph-sdk-javascript library:
const sharedItemId = urlToToSharingToken(url); //1.construct sharing token
const requestUrl = "/shares/" + sharedItemId + "/driveitem/content"; //2. construct a query to download a file content
return new Promise((resolve, reject) => {
var builder = client.api(requestUrl);
getAsBinary(builder, (err, stream) => {
if (err) {
return reject(err);
}
return resolve(stream);
});
});
I am using the Forge data management API to access my A360 files and aim to translate them into the SVF format so that I can view them in my viewer. So far I have been able to reach the desired item using the ForgeDataManagement.ItemsApi, but I don't know what to do with the item to upload it to the bucket in my application.
From the documentation it seems like uploadObject is the way to go (https://github.com/Autodesk-Forge/forge.oss-js/blob/master/docs/ObjectsApi.md#uploadObject), but I don't know exactly how to make this function work.
var dmClient = ForgeDataManagement.ApiClient.instance;
var dmOAuth = dmClient.authentications ['oauth2_access_code'];
dmOAuth.accessToken = tokenSession.getTokenInternal();
var itemsApi = new ForgeDataManagement.ItemsApi();
fileLocation = decodeURIComponent(fileLocation);
var params = fileLocation.split('/');
var projectId = params[params.length - 3];
var resourceId = params[params.length - 1];
itemsApi.getItemVersions(projectId, resourceId)
.then (function(itemVersions) {
if (itemVersions == null || itemVersions.data.length == 0) return;
// Use the latest version of the item (file).
var item = itemVersions.data[0];
var contentLength = item.attributes.storageSize;
var body = new ForgeOSS.InputStream();
// var body = item; // Using the item directly does not seem to work.
// var stream = fs.createReadStream(...) // Should I create a stream object lik suggested in the documention?
objectsAPI.uploadObject(ossBucketKey, ossObjectName, contentLength, body, {}, function(err, data, response) {
if (err) {
console.error(err);
} else {
console.log('API called successfully. Returned data: ' + data);
//To be continued...
}
I hope someone can help me out!
My current data:
ossObjectName = "https://developer.api.autodesk.com/data/v1/projects/"myProject"/items/urn:"myFile".dwfx";
ossBucketKey = "some random string based on my username and id";
Regards,
torjuss
When using the DataManagement API, you can either work with
2 legged oAuth (client_credentials) and access OSS' buckets and objects,
or 3 legged (authorization_code) and access a user' Hubs, Projects, Folders, Items, and Revisions
When using 3 legged, you do access someones content on A360, or BIM360 and these files are automatically translated by the system, so you do not need to translate them again, not to transfer them on a 2 legged application bucket. The only thing you need to do it is get the manifest of the Item or its revision and use the URN to see it in the viewer.
Checkout an example here: https://developer.autodesk.com/en/docs/data/v2/reference/http/projects-project_id-versions-version_id-GET/
you'll see something like
Examples: Successful Retrieval of a Specific Version (200)
curl -X GET -H "Authorization: Bearer kEnG562yz5bhE9igXf2YTcZ2bu0z" "https://developer.api.autodesk.com/data/v1/projects/a.45637/items/urn%3Aadsk.wipprod%3Adm.lineage%3AhC6k4hndRWaeIVhIjvHu8w"
{
"data": {
"relationships": {
"derivatives": {
"meta": {
"link": {
"href": "/modelderivative/v2/designdata/dXJuOmFkc2sud2lwcWE6ZnMuZmlsZTp2Zi50X3hodWwwYVFkbWhhN2FBaVBuXzlnP3ZlcnNpb249MQ/manifest"
}
},
Now, to answer teh other question about upload, I got an example available here:
https://github.com/Autodesk-Forge/forge.commandline-nodejs/blob/master/forge-cb.js#L295. I copied the relevant code here for everyone to see how to use it:
fs.stat (file, function (err, stats) {
var size =stats.size ;
var readStream =fs.createReadStream (file) ;
ossObjects.uploadObject (bucketKey, fileKey, size, readStream, {}, function (error, data, response) {
...
}) ;
}) ;
Just remember that ossObjects is for 2 legged, where as Items, Versions are 3 legged.
We figured out how to get things working after some support from Adam Nagy. To put it simple we had to do everything by use of 3 legged OAuth since all operations involves a document from an A360 account. This includes accessing and showing the file structure, translating a document to SVF, starting the viewer and loading the document into the viewer.
Also, we were targeting the wrong id when trying to translate the document. This post shows how easily it can be done now, thanks to Adam for the info!
I'm trying to write a node.js script that uses a Dynamics NAV Odata feed.
I have both a UserAccount/PW and a Web Services Access Key from my Dynamics NAV setup.
I can't for the life of my find out how to properly authenticate, either by adding something in a header or by adding something in the URL query. I've tried using the 'username:password#server' format. I've tried encoding that as base64 and adding that in the Header for the 'Authentication' value.
The documentation itself is incredibly non-specific. I know how to generate the key, but I don't know how to properly send that key to NAV to authenticate.
I'm using the 'request-promise' npm package, which takes an 'options' argument that I can add arbitrary header key:value pairs into. Please someone give me some direction about how to authenticate to NAV. I've been on this for hours.
I found a satisfactory answer.
Using node-libcurl I was able to cURL to a url using the format
http://username:password#<server>/ODATA_table
specifically my cURL module looks like this:
var Curl = require('node-libcurl').Curl;
var curl = new Curl(),
close = curl.close.bind(curl);
function getOData(url) {
return new Promise((resolve, reject) => {
curl.setOpt(Curl.option.URL, url);
curl.setOpt(Curl.option.HTTPAUTH, Curl.auth.NTLM);
curl.setOpt(Curl.option.SSL_VERIFYPEER, false);
curl.setOpt(Curl.option.SSL_VERIFYHOST, false);
curl.setOpt(Curl.option.POST, 0);
curl.on('end', function (statusCode, body, headers) {
var retObj = JSON.parse(body);
resolve(retObj);
close();
});
curl.on( 'error', function(e){
reject(e);
close();
});
curl.perform();
})
}
module.exports = {getOData: getOData};
But I have to explicitly ask for json in the url, like ?format=json.
Tkol, you're right,also you can use guzzle, it's very simple, that's a sample function that query customers table :
public function ReadCustomer($identifier=0)
{
try {
$client = new GuzzleHttpClient();
$apiRequest = $client->request('GET', 'http://server:port/ServiceName/WS/CompanyName/Page/Customer?$filter=No eq \''.$identifier.'\'',[
'auth' =>'username','password', 'NTLM' ], //NTLM authentication required
'debug' => true //If needed to debug
]);
$content = json_decode($apiRequest->getBody()->getContents());
return $content;
} catch (RequestException $re) {
//For handling exception
}
}
you can check my sample:
update/delete/get from Dynamics NAV OData webservice
I'm using the Users.messages:modify method to apply labels to emails, however, I must refresh the page before the labels which I apply programmatically appear on the gmail user interface.
The desired action is akin to if I manually select a gmail message and then apply a label from the dropdown label applicator at the top of the gmail screen: the label is applied asynchronously. Is this possible to do programmatically?
Code
var applyLabel = function (gapiRequestURL, labelIdsArr)
{
$.ajax({
url: gapiRequestURL,
method: "POST",
contentType: "application/json",
data: JSON.stringify({
addLabelIds: labelIdsArr
}),
success: function(msg){
// alert(JSON.stringify(msg));
},
error: function(msg){
alert("Error:" + JSON.stringify(msg));
}
})
}
var decideWhichLabelToApply = function(messageContentsArr){
var testLabelOne = "Label_12"
var testLabelTwo = "Label_13"
var labelIdsArr = []
for(var i=0; i < messageContentsArr.length; i++){
var currentMessage = messageContentsArr[i]
var messageID = currentMessage.id
if (true){
var labelModifyURL = "https://www.googleapis.com/gmail/v1/users/me/messages/" + messageID + "/modify?access_token=" + thisToken
labelIdsArr.push(testLabelOne)
applyLabel(labelModifyURL, labelIdsArr)
}
else {
var labelModifyURL = "https://www.googleapis.com/gmail/v1/users/me/messages/" + messageID + "/modify?access_token=" + thisToken
labelIdsArr.push(testLabelTwo)
applyLabel(labelModifyURL, labelIdsArr)
}
}
}
Not that I know of. The Gmail web interface does some lazy caching and doesn't seem to notice particularly well changes to the underlying data (i.e. from Inbox, IMAP, API, etc). I believe it doesn't require a full browser (F5) refresh but certainly one needs to do some UI action like clicking on labels or hitting the in-webpage refresh icon for update to show up.
Is it possible to use SharePoint left navigation bar in a "Provider Hosted App". The navigation shown on SharePoint site "PlaceHolderLeftNavBar". Is there any way like some ajax call or REST/CSOM functionality?
According to the official MSDN documentation, the CSOM and JSOM both contain Navigation properties which also provide access to the quick launch menu (aka "left navigation bar").
Links to the docs are as follows:
SP.Navigation.quickLaunch property (sp.js) (JSOM)
Navigation.QuickLaunch property (CSOM)
In order to use either CSOM or JSOM in a provider hosted app, you would need to authenticate using either OAUTH (for Office 365 / SharePoint Online) or by using certificates in a High-Trust / on-premises environment.
If you use the App for SharePoint 2013 template provided by Visual Studio 2013, and select provider-hosted, it should come with a TokenHelper.cs/vb class file which will do much of the heavy lifting for both scenarios. More info on authentication techniques is also available on MSDN - look for the following topics in particular:
Authorization and authentication for apps in SharePoint 2013 How to:
Create high-trust apps for SharePoint 2013 (advanced topic)
I'm not sure if there is a pure REST endpoint available at this time, which could certainly simplify the advanced authorization requirements of CSOM / JSOM in a provider hosted app.
SP.Web.navigation property gets a value that specifies the navigation structure on the site, including the Quick Launch area and the top navigation bar
How to access Navigation (Quick Launch) via CSOM (JavaScript)
function getQuickLaunch(success,error)
{
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var nav = web.get_navigation();
var quickLaunch = nav.get_quickLaunch();
context.load(quickLaunch);
context.executeQueryAsync(
function() {
var nodes = [];
var nodesCount = quickLaunch.get_count();
for(var i = 0; i < nodesCount;i++){
var node = quickLaunch.getItemAtIndex(i);
nodes.push(node);
}
success(nodes);
},
error
);
}
Usage
getQuickLaunch(
function(nodes){
for(var idx in nodes)
{
console.log(nodes[idx].get_title());
}
},
function(sender, args) {
console.log('Error:' + args.get_message());
}
);
How to access Navigation (Quick Launch) via REST
function getQuickLaunch(siteurl, success, failure) {
$.ajax({
url: siteurl + "/_api/web/navigation/quickLaunch",
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
success(data.d.results);
},
error: function (data) {
failure(data);
}
});
}
Usage
getQuickLaunch(_spPageContextInfo.webAbsoluteUrl,
function(nodes){
for(var idx in nodes)
{
console.log(nodes[idx].Title);
}
},
function(error) {
console.log(JSON.stringify(error));
}
);