I am trying to import Attachments/Annotations to CRM Dynamics, I am doing this using the SDK.
I am not using the data import wizard.
I am not individually creating Annotation entities, instead I am using Data Import Feature programmatically.
I mostly leveraged the DataImport sample from the SDK sample code (SDK\SampleCode\CS\DataManagement\DataImport).
Import import = new Import()
{
ModeCode = new OptionSetValue((int)ImportModeCode.Create),
Name = "Data Import"
};
Guid importId = _serviceProxy.Create(import);
_serviceProxy.Create(
new ColumnMapping()
{
ImportMapId = new EntityReference(ImportMap.EntityLogicalName, importMapId),
ProcessCode = new OptionSetValue((int)ColumnMappingProcessCode.Process),
SourceEntityName = sourceEntityName,
SourceAttributeName = sourceAttributeName,
TargetEntityName = targetEntityName,
TargetAttributeName = targetAttributeName
});
I am getting an error "The reference to the attachment could not be found".
The documentation says the crm async service will find the physical file on disk and upload it, my question is where does the async service look for attachment files?
I tried to map documentbody field to the full path of the attachment on the desk, but that still didn't work.
The answer below was provided before the question edits clarifying the use of the import wizard instead of the SDK. The answer below is specific to using the SDK.
When you are attaching files to an Annotation (Note) record in CRM via the SDK, you do use the documentbody attribute (along with mimetype), but you have to first convert it base64.
Something like this:
var myFile = #"C:\Path\To\My\File.pdf";
// Do checks to make sure file exists...
// Convert to Base64.
var base64Data = Convert.ToBase64String(System.IO.File.ReadAllBytes(myFile));
var newNote = new Entity("annotation");
// Set subject, regarding object, etc.
// Add the data required for a file attachment.
newNote.Attributes.Add("documentbody", base64Data);
newNote.Attributes.Add("mimetype", "text/plain"); // This mime type seems to work for all file types.
orgService.Create(newNote);
I found the solution in an obscure blog post, I think the documentation is misleading or unclear, the way this whole thing works, makes having the files available on the server disk for the async to process, odd.
To follow the same principle, all contents should be sent like the csv file itself while being linked to the same import.
To solve this we need create individual special Internal ImportFile for each physical attachment, and link it to the import that has the attachments record details.
As you see below with linking the attachments ImportFile using the ImportId and then setting the two properties (ProcessCode and FileTypeCode), it all worked in the end.
Suffice to say using this method is much more efficient and quicker than individually creating Annotation records.
foreach (var line in File.ReadLines(csvFilesPath + "Attachment.csv").Skip(1))
{
var fileName = line.Split(',')[0].Replace("\"", null);
using (FileStream stream = File.OpenRead(attachmentsPath + fileName))
{
byte[] byteData = new byte[stream.Length];
stream.Read(byteData, 0, byteData.Length);
stream.Close();
string encodedAttachmentData = System.Convert.ToBase64String(byteData);
ImportFile importFileAttachment = new ImportFile()
{
Content = encodedAttachmentData,
Name = fileName,
ImportMapId = new EntityReference(ImportMap.EntityLogicalName, importMapId),
UseSystemMap = true,
ImportId = new EntityReference(Import.EntityLogicalName, importId),
ProcessCode = new OptionSetValue((int)ImportFileProcessCode.Internal),
FileTypeCode = new OptionSetValue((int)ImportFileFileTypeCode.Attachment),
RecordsOwnerId = currentUserRef
};
_serviceProxy.Create(importFileAttachment);
}
idx++;
}
Related
I am able to successfully attach PDF file with ServiceNow table record using GlideSysAttachment API and attachment.write() function in script, however whenever I download and try to open same, I get the error shown in below screenshot.
Code snippet
(function execute() {
try{
var rec = new GlideRecord('incident');
var attachment = new GlideSysAttachment();
var incidentSysID = incident.number;
rec.get(incidentSysID);
var fileName = 'Test_Incident.pdf';
var contentType = 'application/pdf'; // Also tried with contentType as 'text/pdf'
var content = pdf_content;
var agr = attachment.write(rec, fileName, contentType, content);<br>
gs.info('The PDF attachment sys_id is: ' + agr);
}catch(err){
gs.log('Got Error: ' + err);
gs.info(err);
}
})()
I also tried "AttachmentCreator" with ecc_queue within script but same error occurs. Below is code for it.
(function execute()
{var attCreator = new GlideRecord('ecc_queue');
attCreator.agent = "AttachmentCreator";
attCreator.topic = "AttachmentCreator";
attCreator.name = "Test.pdf" + ":" + "text/pdf";
//Also tried, "Test.pdf:application/pdf"
attCreator.source = "incident"+":"+ incident.number;
// Record Table name and sys_id of the particular record
var content = pdf_content; // pdf_content is any string variable
var stringUtil = new GlideStringUtil();
var base64String = stringUtil.base64Encode(content);
var isValid=GlideStringUtil.isBase64(base64String);
var base64String= gs.base64Encode(content);
gs.info("Is valid base64 format in ecc_queue ? "+ isValid);
attCreator.payload = base64String; //base64 content of the file
attCreator.insert();
})()
I am able to attach and view excel and word files with similar scripts without any issues. I have checked system properties for attachments but everything looks fine. I am able to view the PDF file uploaded from UI to particular table records however not the one I attach via REST API or scripts.
I have also tried sending encoded data as bytes, base64 or simple string but nothing seems to work. I don't get any errors and attachment id is returned each time on creation of attachment.
After modifying my code slightly for above functions w.r.t scoped application instead of global; I got some information from logs when I debug:
05:38:38.411 Security restricted: File type is not allowed or does not match the content for file Test.pdf
05:38:38.410 Security restricted: MIME type mismatch for file: Test.pdf. Expected type:application/pdf, Actual type: text/plain
05:38:38.394 App:XYZ App x_272539_xyz_ap: Is valid base64 format in ecc_queue ? true
First a comment: This line in your code is accidentally working -- make sure you understand that a task number is not the object sys_id
var incidentSysID = incident.number; // should be incident.sys_id
Next, it's unclear where the PDF content is coming from. IF your complete code is listed, I would expect the errors given as you have noted that pdf_content is "any string variable."
ServiceNow does have a the capability to create a PDF from an HTML argument.
Generating a PDF from HTML
Here's a helpful blog post for getting a PDF (Platform generated) of an existing record:
Love PDF? PDF loves you too
I'm trying to work with text files in the apps folder.
Here's my GoogleApiClient constructor:
googleApiClient = new GoogleApiClient.Builder(this)
.AddApi(DriveClass.API)
.AddScope(DriveClass.ScopeFile)
.AddScope(DriveClass.ScopeAppfolder)
.UseDefaultAccount()
.AddConnectionCallbacks(this)
.EnableAutoManage(this, this)
.Build();
I'm connecting with:
googleApiClient.Connect()
And after:
OnConnected()
I need to list all files inside the app folder. Here's what I got so far:
IDriveFolder appFolder = DriveClass.DriveApi.GetAppFolder(googleApiClient);
IDriveApiMetadataBufferResult result = await appFolder.ListChildrenAsync(googleApiClient);
Which is giving me the files metadata.
But after that, I don't know how to read them, edit them or save new files. They are text files created with my app's previous version (native).
I'm following the google docs for drive but the Xamarin API is a lot different and has no docs or examples. Here's the API I'm using: https://components.xamarin.com/view/googleplayservices-drive
Edit:
Here is an example to read file contents from the guide:
DriveFile file = ...
file.open(mGoogleApiClient, DriveFile.MODE_READ_ONLY, null)
.setResultCallback(contentsOpenedCallback);
First I can't find anywhere in the guide what "DriveFile file = ..." means. How do I get this instance? DriveFile seems to be a static class in this API.
I tried:
IDriveFile file = DriveClass.DriveApi.GetFile(googleApiClient, metadata.DriveId);
This has two problems, first it complains that GetFile is deprecated but doesn't say how to do it properly. Second, the file doesn't have an "open" method.
Any help is appreciated.
The Xamarin binding library wraps the Java Drive library (https://developers.google.com/drive/), so all the guides/examples for the Android-based Drive API work if you keep in mind the Binding's Java to C# transformations:
get/set methods -> properties
fields -> properties
listeners -> events
static nested class -> nested class
inner class -> nested class with an instance constructor
So you can list the AppFolder's directory and files by recursively using the Metadata when the drive item is a folder.
Get Directory/File Tree Example:
await Task.Run(() =>
{
async void GetFolderMetaData(IDriveFolder folder, int depth)
{
var folderMetaData = await folder.ListChildrenAsync(_googleApiClient);
foreach (var driveItem in folderMetaData.MetadataBuffer)
{
Log.Debug(TAG, $"{(driveItem.IsFolder ? "(D)" : "(F)")}:{"".PadLeft(depth, '.')}{driveItem.Title}");
if (driveItem.IsFolder)
GetFolderMetaData(driveItem.DriveId.AsDriveFolder(), depth + 1);
}
}
GetFolderMetaData(DriveClass.DriveApi.GetAppFolder(_googleApiClient), 0);
});
Output:
[SushiHangover.FlightAvionics] (D):AppDataFolder
[SushiHangover.FlightAvionics] (F):.FlightInstrumentationData1.json
[SushiHangover.FlightAvionics] (F):.FlightInstrumentationData2.json
[SushiHangover.FlightAvionics] (F):.FlightInstrumentationData3.json
[SushiHangover.FlightAvionics] (F):AppConfiguration.json
Write a (Text) File Example:
using (var contentResults = await DriveClass.DriveApi.NewDriveContentsAsync(_googleApiClient))
using (var writer = new OutputStreamWriter(contentResults.DriveContents.OutputStream))
using (var changeSet = new MetadataChangeSet.Builder()
.SetTitle("AppConfiguration.txt")
.SetMimeType("text/plain")
.Build())
{
writer.Write("StackOverflow Rocks\n");
writer.Write("StackOverflow Rocks\n");
writer.Close();
await DriveClass.DriveApi.GetAppFolder(_googleApiClient).CreateFileAsync(_googleApiClient, changeSet, contentResults.DriveContents);
}
Note: Substitute a IDriveFolder for DriveClass.DriveApi.GetAppFolder to save a file in a subfolder of the AppFolder.
Read a (text) File Example:
Note: driveItem in the following example is an existing text/plain-based MetaData object that is found by recursing through the Drive contents (see Get Directory/File list above) or via creating a query (Query.Builder) and executing it via DriveClass.DriveApi.QueryAsync.
var fileContexts = new StringBuilder();
using (var results = await driveItem.DriveId.AsDriveFile().OpenAsync(_googleApiClient, DriveFile.ModeReadOnly, null))
using (var inputStream = results.DriveContents.InputStream)
using (var streamReader = new StreamReader(inputStream))
{
while (streamReader.Peek() >= 0)
fileContexts.Append(await streamReader.ReadLineAsync());
}
Log.Debug(TAG, fileContexts.ToString());
I have an endpoint that needs to accept a file upload and also some other information from the client request. With the following code I can upload the file successfully but can't seem to figure out how to read the other info.
I make a test request from Postman with the following form data:
image -- myimage.jpg -- of type File
email -- a#b.com -- of type Text
The backend code looks like this:
[HttpPost]
public async Task<HttpResponseMessage> SharePhoto()
{
try
{
var provider = new MultipartMemoryStreamProvider();
var data = await Request.Content.ReadAsMultipartAsync(provider);
// this is how I get the image which I am succesfully passing to EmailService
var item = (StreamContent)provider.Contents[0];
using (var stream = new MemoryStream())
{
await item.CopyToAsync(stream);
String emailAddress;
EmailService.SendSharedPhoto(emailAddress, stream);
return Request.CreateResponse();
}
}
catch
{
// do stuff
}
}
In this example I am able to access provider.Contents[1] but can't seem to be able to get the value from it into emailAddress. I'm thinking it may be possible to use the same trick as the await item.CopyToASync(stream) from the image upload, but I'm hoping I can get a simpler solution to that. Any ideas?
I just barely answered a very similar question to this yesterday. See my answer here complete with sample controller code.
The method I ended up using is:
If the form elements are strings (and it worked for me since the mobiel frontend took responsability for input data) you can do this:
var streamContent = (StreamContent)provider.Contents[1];
var memStream = new MemoryStream();
await streamContent.CopyToAsync(memStream);
var actualString = Encoding.UTF8.GetString(x.ToArray());
If however the field needs to represent a collection of items, like for example the email list: ["a#b.com", "x#c.com"], etc a JavaScriptSerializer can be user, like so:
var streamContent = (StreamContent)provider.Contents[1];
var emailAddresses = await str.ReadAsStringAsync();
var jsSerializer = new JavaScriptSerializer();
var deserializedData = jsSerializer.Deserialize<string[]>(emailAddresses);
Note that this is nowhere near safe, though it is few lines of code and happens to work.
I'm trying to send content to a campaign template in MailChimp using MailChimp.net API. My template contains `
<b>mc:edit="question"</b>`
And the MailChimp API wants a JSON string in a struct/associative array. However, I can't seem to get the data to show up in the editable section and I've tried several different ways:
CampaignSegmentOptions segmentOptions = new CampaignSegmentOptions();
segmentOptions.Match = "All";
CampaignCreateContent content = new CampaignCreateContent();
content.HTML = "<p>Testing</p>";
CampaignCreateOptions options = new CampaignCreateOptions();
options.Title = "Testing";
options.ListId = listID;
options.ToName = "Test Name";
options.FromEmail = "user#example.com";
options.FromName = "Testing from Example.com";
options.Subject = "Test Subject";
options.FacebookComments = false;
//Using struct attempt
sections s = new sections();
s.question = "What did the lion need?";
//Using string attempt
//String[] s = new string [] {"question\":\"What did the lion need?"};
//Using dictionary attempt
Dictionary<string,string> dict = new Dictionary<string,string>();
dict.Add("question","What did the lion need?");
content.Sections = dict;
Campaign result = mc.CreateCampaign("regular", options, content);
Any suggestions? Thank you in advance.
Solved. In my case the Dictionary approach was the only one that worked. I found that whenever I edited an existing MailChimp template, MailChimp appeared to rewrete my mc:edit fields after I saved them and added their own code making my mc:edit fields break. However, if I exported one of their templates to my desktop, and then imported it back as a new template, I found that editing the template on their website no longer changed my code after saving. I remember seeing a note in the documentation about exporting and importing, but nothing that implied that was the only way to keep my fields intact.
My project is almost done, and thanks to stackoverflow. Now that I have managed to capture Users details and their certificates, I am looking for a way to generate pdf which I will send to those who passed.
I am not looking for the code at the moment. I have an asp.net mvc 3 application which shows Certificates details for my students. Now I want the certificates to be generated then sent by email, all this automated.
First I would like help on how I can generate a pdf from database values and sending the generated pdf wont be a problem.
Using iTextSharp, this code will create and serve a PDF:
public FileStreamResult DownloadPDF()
{
MemoryStream workStream = new MemoryStream();
using(Document document = new Document())
{
PdfWriter.GetInstance(document, workStream).CloseStream = false;
document.Open();
document.SetPageSize(PageSize.LETTER);
document.SetMargins(12, 12, 8, 7);
document.NewPage();
// Create a new Paragraph object with the text, "Hello, World!"
var welcomeParagraph = new Paragraph("Hello, World!");
// Add the Paragraph object to the document
document.Add(welcomeParagraph);
// This is where your data would go
document.Close();
}
workStream.Position = 0;
FileStreamResult fileResult = new FileStreamResult(workStream, "application/pdf");
fileResult.FileDownloadName = "test.pdf";
return fileResult;
}
For more information see Creating PDF Documents with ASP.NET and iTextSharp
There are a lot of tutorials online, but this should get you started.