API Questions on Kentico API.
Version 5.x > API > DocumentHelper.AddAttachment View modes: 
User avatar
Member
Member
andrejg - 3/14/2013 12:41:11 PM
   
DocumentHelper.AddAttachment
Hi,

I'm trying to add an attachment via DocumentHelper.AddAttachment to an existing node in Kentico. The attachment is an image stored in another database which I read with a Linq to SQL query, which returns a Linq Binary object that I can use to create a byte array, which, in theory, could be passed on to the AttachmentInfo (as Martin suggested in this old forum post).

The problem I have is that CMS.WorkFlowEngine.DocumentHelper.AddAttachment requires the attachment to be either a string with the path to the file or a HttpPostedFile object. If I set the file to an empty string the program just breaks and says it can't find the specified file.

The current workaround is that I read the image from the other database, save it to a file, then pass the file's path to the AddAttachment method. After the node is updated the file is deleted from disk.

Is there a way of doing this in memory, i.e. without saving the file to disk? I've tried creating an instance of a HttpPostedFile via this hack, however the program crashes with an "Operation is not valid due to the current state of the object" error.

Code that's not working:
CMS.SiteProvider.UserInfo ui = CMS.SiteProvider.UserInfoProvider.GetUserInfo("user");
CMS.TreeEngine.TreeProvider tree = new CMS.TreeEngine.Tree(ui);

//create the document that I want to attach the image to:
TreeNode n = new TreeNode("documentType", tree);
TreeNode p = tree.SelectSingleNode("website", "article/path", CMS.TreeEngine.TreeProvider.ALL_CULTURES, true, null, false);

n.DocumentName = "foo";
//omitted: code that adds the data to the document

//insert the document
CMS.WorkFlowEngine.DocumentHelper.InsertDocument(n, p.NodeId, tree);

//read the image from the other database:
Binary file = (from img in _db.images where img.id == imageId select a.data).SingleOrDefault();

//create an instance of a HttpPostedFile, see bottom for the ConstructHttpPostedFile method
HttpPostedFile postedFile = ConstructHttpPostedFile(file.ToArray(), Guid.NewGuid().ToString() + ".jpg", "image/jpeg");

//create the attachment
AttachmentInfo ai = null;
ai = CMS.WorkFlowEngine.DocumentHelper.AddAttachment(n, "guidColumnName", postedFile, tree);
//an alternative without the HttpPostedFile would be:
//ai = CMS.WorkFlowEngine.DocumentHelper.AddAttachment(n, "guidColumnName", "", tree);
//however that's also not working
ai.AttachmentMimeType = "image/jpeg";
ai.AttachmentBinary = file.ToArray();
ai.Update();
n.Update();

//method from http://stackoverflow.com/questions/5514715/how-to-instantiate-a-httppostedfile
public HttpPostedFile ConstructHttpPostedFile(byte[] data, string filename, string contentType)
{
// Get the System.Web assembly reference
Assembly systemWebAssembly = typeof(HttpPostedFileBase).Assembly;
// Get the types of the two internal types we need
Type typeHttpRawUploadedContent = systemWebAssembly.GetType("System.Web.HttpRawUploadedContent");
Type typeHttpInputStream = systemWebAssembly.GetType("System.Web.HttpInputStream");

// Prepare the signatures of the constructors we want.
Type[] uploadedParams = { typeof(int), typeof(int) };
Type[] streamParams = { typeHttpRawUploadedContent, typeof(int), typeof(int) };
Type[] parameters = { typeof(string), typeof(string), typeHttpInputStream };

// Create an HttpRawUploadedContent instance
object uploadedContent = typeHttpRawUploadedContent
.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, uploadedParams, null)
.Invoke(new object[] { data.Length, data.Length });

// Call the AddBytes method
typeHttpRawUploadedContent
.GetMethod("AddBytes", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(uploadedContent, new object[] { data, 0, data.Length });

// Create an HttpInputStream instance
object stream = (Stream)typeHttpInputStream
.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, streamParams, null)
.Invoke(new object[] { uploadedContent, 0, data.Length });

// Create an HttpPostedFile instance
HttpPostedFile postedFile = (HttpPostedFile)typeof(HttpPostedFile)
.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, parameters, null)
.Invoke(new object[] { filename, contentType, stream });

return postedFile;
}

Workaround:
CMS.SiteProvider.UserInfo ui = CMS.SiteProvider.UserInfoProvider.GetUserInfo("user");
CMS.TreeEngine.TreeProvider tree = new CMS.TreeEngine.Tree(ui);

//create the document that I want to attach the image to:
TreeNode n = new TreeNode("documentType", tree);
TreeNode p = tree.SelectSingleNode("website", "article/path", CMS.TreeEngine.TreeProvider.ALL_CULTURES, true, null, false);

n.DocumentName = "foo";
//omitted: code that adds the data to the document

//insert the document
CMS.WorkFlowEngine.DocumentHelper.InsertDocument(n, p.NodeId, tree);

//read the image from the other database:
Binary file = (from img in _db.images where img.id == imageId select a.data).SingleOrDefault();

//save the image to disk
string fileName = Guid.NewGuid().ToString() + ".jpg";
FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
byte[] b = file.ToArray();
fs.Write(b, 0, b.Length);
fs.Close();

//create the attachment
AttachmentInfo ai = null;
ai = CMS.WorkFlowEngine.DocumentHelper.AddAttachment(n, "guidColumnName", fileName, tree);
ai.AttachmentMimeType = "image/jpeg";
ai.AttachmentBinary = file.ToArray();
ai.Update();
n.Update();

//delete the image
File.Delete(fileName);

Note that this is a C# console project with references added to Kentico's API libraries.

So, is there any way of avoiding the unnecessary disk writes?

Thanks,
Andrej

User avatar
Kentico Support
Kentico Support
kentico_filipl - 3/18/2013 8:57:50 AM
   
RE:DocumentHelper.AddAttachment
Hi Andrej,

I think you pass wrong parameters to the DocumentHelper.AddAttachment() method on this line:
//ai = CMS.WorkFlowEngine.DocumentHelper.AddAttachment(n, "guidColumnName", "", tree); 

The parameters for this method should be:
DocumentHelper.AddAttachment(TreeNode node, String guidColumnName, String file, TreeProvider tree)
where:
node - document node
guidColumnName - column containing the Attachment GUID
file - attachment file path
tree - treeProvider to use

Best regards,
Filip Ligac

User avatar
Member
Member
andrejg - 3/18/2013 9:23:36 AM
   
RE:DocumentHelper.AddAttachment
Hi Filip,

yes, I know that. It was just an example.

I already have the binary of the image in memory, it seems a waste to save every image to
disk, upload it to the CMS and then delete it from disk.

User avatar
Kentico Support
Kentico Support
kentico_filipl - 3/22/2013 2:44:42 AM
   
RE:DocumentHelper.AddAttachment
Hi Andrej,

You can also use AttachmentInfo object to assign attachment to a document.
All you need to do is create a new instance of AttachmentInfo object and put the byte array of the image into AttachmentBinary property and also set other properties you need correctly (like AttachmentDocumentID, AttachmentID, AttachmentName, etc.).

Here is an example:
AttachmentInfo newAttachment = new AttachmentInfo
{
AttachmentBinary = imageBinary,
AttachmentDocumentID = 25,
AttachmentExtension = "jpg",
AttachmentSize = imageBinary.Length,
AttachmentName = "newAttachment",
AttachmentMimeType = MimeTypeHelper.GetMimetype("jpg"),
AttachmentTitle = "attachmentTitle",
AttachmentDescription = "",
AttachmentSiteID = CMSContext.CurrentSiteID,
AttachmentGUID = Guid.NewGuid()
};

Then you can call AddAttachment() method with following parameters:
AddAttachment(
TreeNode node,
string guidColumnName,
Guid attachmentGuid,
Guid groupGuid,
Object file,
TreeProvider tree,
int width,
int height,
int maxSideSize
)

where Object file is your byte array of the image.

All information about API methods can be found in documentation, if you are using version 5.5R2, you can download it here.

Best regards,
Filip Ligac


User avatar
Member
Member
andrejg - 6/10/2013 6:40:55 AM
   
RE:DocumentHelper.AddAttachment
I have a new problem regarding attachments.

My code works OK when the program is run on Windows 7 machines, however when I run the program on a Windows Server 2008 R2 (64-bit, Enterprise, used as a Terminal Server, if it matters) the program crashes at

AttachmentInfo ai = null;
ai = DocumentHelper.AddAttachment(node, GuidColumnName, FileName, _tree);

with a System.IO.IOException: The device is not ready.

At first I thought that is a filesystem error, but that's not it. The program has permission and can read/write to the specified temp directory where the file I'm trying to attach is stored.

I've tried adding attachments differently:
AttachmentManager am = new AttachmentManager(_tree.Connection);
Guid AttachmentGuid = Guid.NewGuid();
AttachmentInfo ai = new AttachmentInfo(FileName, node.DocumentID, AttachmentGuid, _tree.Connection);
am.SetAttachmentInfo(ai);
node.SetValue(GuidColumnName, AttachmentGuid);

This code, however, crashes at am.SetAttachmentInfo(ai) (essentially the same place) with the same error.
Other 'parts' of the Kentico API work just fine (inserting/updating/deleting documents)...

The full error follows:
System.IO.IOException: The device is not ready.

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj)
at System.IO.Directory.CreateDirectory(String path)
at CMS.DirectoryProviderDotNet.DirectoryProvider.CreateDirectory(String path)
at CMS.DirectoryUtilities.DirectoryHelper.CreateDirectory(String path)
at CMS.DirectoryUtilities.DirectoryHelper.EnsureDiskPath(String path, String startingPath)
at CMS.FileManager.AttachmentManager.SaveFileToDisk(String siteName, String guid, String fileName, String fileExtension, Object fileData, Boolean deleteOldFiles, Boolean synchronization)
at CMS.FileManager.AttachmentManager.SaveFileToDisk(string siteName, String guid, String fileName, String fileExtension, Object FileData, Boolean deleteOldFiles)
at CMS.FileManager.AttachmentManager.SetAttachmentInfo(AttachmentInfo attachment)

User avatar
Kentico Support
Kentico Support
kentico_filipl - 6/10/2013 9:15:14 AM
   
RE:DocumentHelper.AddAttachment
Hi Andrej,

Could you make sure your server is set up correctly?

This error occurs when partition on which you are trying to write is configured wrongly, e.g. as a CD drive, not Hard Drive.

Best regards,
Filip Ligac

User avatar
Member
Member
andrejg - 6/11/2013 3:55:33 AM
   
RE:DocumentHelper.AddAttachment
The hard disk is OK - it's reported as a HDD. I've even added a smallish extra HDD (it's a virtualized server) to use as a temporary drive, though it turned out it was unnecessary.

I do think that there's something wrong with the server, since I rebooted it when there were no users connected and now the program works fine. It's really odd. According to the Windows logs everything is fine and dandy...

I have yet to test it with a normal user over a Terminal Services session, but as an administrator over RDP it works fine.

Are there any other circumstances that would trigger such an error, besides a misconfigured drive?

User avatar
Member
Member
andrejg - 6/12/2013 2:48:51 AM
   
RE:DocumentHelper.AddAttachment
I have another really weird thing happening. The program I wrote is used for publishing articles from a newsroom editorial system (Tera GoodNews 3).

It's a WPF application that reads (most of) the article data from the editorial database, transforms their XML, resizes and crops the pictures and inserts the whole thing into Kentico via the API. It is run locally from the editorial staff workstations.

So far, so good.

I have Kentico set up so that it stores the files in the file system (files folder: d:\gnweb_files). Here's the weird thing: on every workstation the program is run it creates a copy of the folder Kentico stores its files in. So every workstation has a d:\gnweb_files folder.
It's not identical to the files folder on the server where Kentico is installed. It seems to me that every image that has been uploaded to Kentico via the API is stored locally on the user's workstation. Or a copy of it is stored, anyway. I have yet to see any ill effects of this (apart of the user's hard drive filling up). Maybe we just haven't noticed :/.

Is there a way to avoid this, besides setting Kentico to store the files in the database? Or rewriting the program so that is run on the server where Kentico is located and triggering the publish procedure remotely?

I'd really hate to give up on this and go back to the old publishing methods since your API is roughly 5 times faster (have I mentioned how neat it is? :)). And that's not even counting the time saved with the automatic image preparation...

User avatar
Member
Member
andrejg - 6/12/2013 4:33:29 AM
   
RE:DocumentHelper.AddAttachment
Another thing I noticed is that now, apparently, the attachments are actually stored in the database, even though Kentico is set up to save the files in the file system.
At least it looks like that to me - the AttachmentBinary field in the CMS_Attachment table is not null, it actually contains the photo.

User avatar
Kentico Support
Kentico Support
kentico_filipl - 6/14/2013 5:59:54 AM
   
RE:DocumentHelper.AddAttachment
Hi Andrej,

How do you have your server and editorial staff workstations connected? Are you using Web farm synchronization module?

Additionally, do you have file storage options set to database and file system, too? In that case the behaviour you are experiencing is correct, i.e. the program creates a folder for uploaded files locally on each machine and also in database. It makes sense since storing them locally should bring the best performance, that is why they need to be saved on disk. You can check details about storing files in Developer's Guide - Where the files are stored.

Best regards,
Filip Ligac

User avatar
Member
Member
andrejg - 6/14/2013 7:20:48 AM
   
RE:DocumentHelper.AddAttachment
> How do you have your server and editorial staff workstations connected? Are you using Web farm synchronization module?

The workstations are just normal Windows 7 computers, Kentico isn't installed on any of them. I just created the publishing application according to the Using API and CMS Controls outside the CMS project guide.
The editorial staff runs it on their laptops/desktops; it reads some article content from XML files, some content from the editorial database, the pictures from a network path, transforms the data so it's usable and resizes and crops the pictures. When all this is done it creates or updates the TreeNodes in the appropriate locations and creates the links so the articles are displayed on the web page.

Have I stumbled into a, shall we say, unsupported scenario? :P

I probably could rework the program so that it runs on the webserver (as a web service, or something) so that when the users publish the articles from our editorial software it would just send what to publish and where to put it (a bunch of article ids and a node alias path, in essence). That would solve this conundrum, I think.

> Additionally, do you have file storage options set to database and file system, too?

No, I disabled that as soon as I found the option :).

User avatar
Kentico Support
Kentico Support
kentico_filipl - 6/14/2013 9:19:28 AM
   
RE:DocumentHelper.AddAttachment
Hi Andrej,

If those file storage options for database and file system are both disabled, this issue will be probably related to your publishing application.

However, before proceeding any further, could you confirm you are using the latest hotfix for version 5.5R2 (5.5R2.44) just to eliminate the fact that it could be caused by a bug.

If so, don't you accidentally save any of those processed files on the disk? Would it be also possible to post some code snippet which handles this process?

Best regards,
Filip Ligac

User avatar
Member
Member
andrejg - 6/14/2013 10:55:26 AM
   
RE:DocumentHelper.AddAttachment
> If those file storage options for database and file system are both disabled, this issue will be probably related to your publishing application.

The options are as follows: Store files in file system: true, Store files in database: false, Files folder: d:\gnweb_files, Redirect files to disk: true. Sorry for the mixup.

> However, before proceeding any further, could you confirm you are using the latest hotfix for version 5.5R2 (5.5R2.44) just to eliminate the fact that it could be caused by a bug.

No, we are actually using 5.5.3926... I'll upgrade my testing server to the latest 5.5r2 and see what it does. Or at least I'll try to - we use some addons for Kentico made by Tera Digital Publishing and they break when upgrading. I think I can safely remove them, though, since I won't be needing them anymore. I'll let you know.

> If so, don't you accidentally save any of those processed files on the disk? Would it be also possible to post some code snippet which handles this process?

The pictures are processed entirely in memory, nothing is written on disk when cropping and resizing.
The only time the program writes a picture to the disk is right before it calls DocumentHelper.AddAttachment and it deletes the image afterwards. Besides my temporary directory is c:\temp, the files appearing on the user machines are in d:\gnweb_files (with the exact same file/directory structure as Kentico uses: 00 to ff, files are named something like 000a9d72-b094-4453-9e7b-85688e304e91.jpg).
Also, when I tried running my program on our Terminal Services server it broke on DocumentHelper.AddAttachment since the server didn't have a d: drive.

Here's a snippet:
private void WriteImageToDisk(System.Data.Linq.Binary binary, string f)
{
try
{
FileStream fs = new FileStream(f, FileMode.Create, FileAccess.Write);
byte[] b = binary.ToArray();
fs.Write(b, 0, b.Length);
fs.Close();
fs.Dispose();
}
catch (Exception ex)
{
string ErrorMessage = String.Format("{1}{0}{2}{0}{3}{0}{4}", Environment.NewLine, "Helper.cs/WriteImageToDisk", ex.ToString(),f, binary.ToString());
MessageBox.Show(ErrorMessage);
}
}

private void AddAttachment(TreeNode node, string GuidColumnName, string FileName, byte[] p)
{
try
{
AttachmentInfo ai = null;
ai = DocumentHelper.AddAttachment(node, GuidColumnName, FileName, _tree);
ai.AttachmentMimeType = "image/jpeg";
ai.AttachmentBinary = p;
ai.Update();
}
catch (Exception ex)
{
string ErrorMessage = String.Format("{1}{0}NodeName: {2}{0}GuidColumnName: {3}{0}FileName: {4}{0}{5}", Environment.NewLine, "Helper.cs/AddAttachment", node.NodeName, GuidColumnName, FileName, ex.ToString());
MessageBox.Show(ErrorMessage);
}
}
//code that adds the photo documents and adds attachments to them:
Photo photo = new Photo(ImageId, ArticleId);

//TempDir is C:\temp
string file01 = TempDir + Guid.NewGuid().ToString() + ".jpg";
string file02 = TempDir + Guid.NewGuid().ToString() + ".jpg";
string file03 = TempDir + Guid.NewGuid().ToString() + ".jpg";

WriteImageToDisk(photo.VarBig, file01);
WriteImageToDisk(photo.VarMedium, file02);
WriteImageToDisk(photo.VarSmall, file03);

TreeNode Gn3Picture = new TreeNode("tera.gn3picture", _tree);

Gn3Picture.DocumentName = photo.Name;
//omitted all the code that sets the content of the Gn3Picture object (captions, authors, etc)

DocumentHelper.InsertDocument(Gn3Picture, ImageParent.NodeID, _tree);

//here we add the actual pictures as attachments
AddAttachment(Gn3Picture, "FileAttachment", file01, photo.VarBig.ToArray());
AddAttachment(Gn3Picture, "FileThumbnail", file03, photo.VarSmall.ToArray());

DocumentHelper.UpdateDocument(Gn3Picture, _tree);

if (File.Exists(file01))
{
File.Delete(file01);
}
if (File.Exists(file02))
{
File.Delete(file02);
}
if (File.Exists(file03))
{
File.Delete(file03);
}

User avatar
Member
Member
andrejg - 6/16/2013 1:23:07 PM
   
RE:DocumentHelper.AddAttachment
Hi Filip,

I've created a new server with a fresh installation of Kentico 5.5.3996 R2, imported the document types that I create with the API and set the file storage options like we have in our production servers (store files in file system, don't store the files in the database and redirect files to disk).

I've recompiled the program with the 5.5r2 assemblies, ran it and, sure enough, the files get stored on the file system of the computer where the program is running. On the server they are stored in the database and are written to disk only when first opened (because of the 'redirect files to disk' option is set).

Kentico's file/folder structure gets created with the execution of DocumentHelper.AddAttachment.

User avatar
Member
Member
andrejg - 6/17/2013 10:05:09 AM
   
RE:DocumentHelper.AddAttachment
I've also tried with Kentico 7 and the same thing happens.

User avatar
Kentico Support
Kentico Support
kentico_filipl - 6/28/2013 1:38:12 AM
   
RE:DocumentHelper.AddAttachment
Hello Andrej,

It is quite possible this behaviour is related to Redirect files to disk property because as is it is stated in documentation - if checked, file requests are redirected to the physical file in the file system if possible which seems to be this case. Have you tried setting it to false? Does it occur even then?

I will consult this issue with the leading developer as soon as possible and let you know about whether this is the expected behaviour by design or some sort of a bug. Unfortunately, he is currently on vacation so I am afraid it will have to wait until he comes back.

Thank you for your patience.

Best regards,
Filip Ligac

User avatar
Member
Member
andrejg - 6/30/2013 12:32:04 PM
   
RE:DocumentHelper.AddAttachment
Hi Filip,
yes, even with redirect files to disk unchecked the attachments still get written to the local machine's hdd.

> Unfortunately, he is currently on vacation so I am afraid it will have to wait until he comes back.

No worries (not much, anyway :P). I'm starting to rewrite my program so that all of the processing will be done on the server - the client on the user workstations will only trigger the publish process and pass the article data on to the server. The actual upload of images via the Kentico API will be done server-side, so it should take care of this problem nicely.

I'll be needing a way to dump all the image binaries from the database to hdd (a programmatical equivalent of visiting a page with Redirect files to disk enabled), but that's a problem for another day :).

Thank you very much for your help, I really appreciate it.

User avatar
Kentico Support
Kentico Support
kentico_filipl - 7/1/2013 1:34:32 AM
   
RE:DocumentHelper.AddAttachment
Hi Andrej,

You are welcome. I hope you will be successful and refactoring will work.

If you need any more help, do not hesitate to write.

Best regards,
Filip Ligac