Transferring binary data to/from WebServices (and to/from COM (Automation) objects)
A number of people have asked for guidance on how to transfer data to/from COM and WebServices in NAV 2009.
In the following I will go through how to get and set a picture on an item in NAV through a Web Service Connection.
During this scenario we will run into a number of obstacles - and I will describe how to get around these.
First of all - we want to create a Codeunit, which needs to be exposed to WebServices. Our Codeunit will contain 2 functions: GetItemPicture and SetItemPicture - but what is the data type of the Picture and how do we return that value from a WebService function?
The only data type (supported by Web Services) that can hold a picture is the BigText data type.
We need to create the following two functions:
GetItemPicture(No : Code[20];VAR Picture : BigText)
SetItemPicture(No : Code[20]; Picture : BigText);
BigText is capable if holding binary data (including null terminals) up to any size. On the WSDL side these functions will have the following signature:
As you can see BigText becomes string - and strings in .net are capable of any size and any content.
The next problem we will face is, that pictures typically contains all kinds of characters, and unfortunately when transferring strings to/from WebServices there are a number of special characters that are not handled in the WebServices protocol.
(Now you wonder whether you can have <> in your text - but that isn't the problem:-)
The problem is LF, CR, NULL and other characters like that.
So we need to base64 (or something like that) encode our picture when returning it from Web Services. Unfortunately I couldn't find any out-of-the-box COM objects that was able to do base64 encoding and decoding - but we can of course make one ourselves.
Lets assume for a second that we have a base64 COM object - then this would be our functions in AL:
GetItemPicture(No : Code[20];VAR Picture : BigText)
CLEAR(Picture);
Item.SETRANGE(Item."No.", No, No);
IF (Item.FINDFIRST()) THEN
BEGIN
Item.CALCFIELDS(Item.Picture);
// Get Temp FileName
TempFile.CREATETEMPFILE;
FileName := TempFile.NAME;
TempFile.CLOSE;
// Export picture to Temp File
Item.Picture.EXPORT(FileName);
// Get a base64 encoded picture into a string
CREATE(base64);
Picture.ADDTEXT(base64.encodeFromFile(FileName));
// Erase Temp File
FILE.ERASE(FileName);
END;
SetItemPicture(No : Code[20];Picture : BigText)
Item.SETRANGE(Item."No.", No, No);
IF (Item.FINDFIRST()) THEN
BEGIN
// Get Temp FileName
TempFile.CREATETEMPFILE;
FileName := TempFile.NAME;
TempFile.CLOSE;
// Decode the bas64 encoded image into the Temp File
CREATE(base64);
base64.decodeToFile(Picture, FileName);
// Import picture from Temp File
Item.Picture.IMPORT(FileName);
Item.Modify();
// Erase Temp File
FILE.ERASE(FileName);
END;
A couple of comments to the source:
- The way we get a temporary filename in NAV2009 is by creating a temporary file, reading its name and closing it. CREATETEMPFILE will always create new GUID based temporary file names - and the Service Tier will not have access to write files in e.g. the C:\ root folder and a lot of other places.
- The base64 automation object is loaded on the Service Tier (else it should be CREATE(base64, TRUE, TRUE);) and this is the right location, since the exported file we just stored is located on the Service Tier.
- The base64.encodeFromFile reads the file and returns a very large string which is the picture base64 encoded.
- The ADDTEXT method is capable of adding these very large strings and add them to a BigText (BTW - that will NOT work in the classic client).
- We do the cleanup afterwards - environmental protection:-)
So, why does the ADDTEXT support large strings?
As you probably know, the ADDTEXT takes a TEXT and a position as parameter - and a TEXT doesn't allow large strings, but what happens here is, that TEXT in C# becomes string - and the length-checking of TEXT variables are done when assigning variables or transferring parameters to functions and the ADDTEXT doesn't check for any specific length (which comes in handy in our case).
The two lines in question in C# looks like:
base64.Create(DataError.ThrowError);
picture.Value = NavBigText.ALAddText(picture.Value, base64.InvokeMethod<String>(@"encodeFromFile", fileName));
Note also that the base64.decodeToFile function gets a BigText directly as parameter. As you will see, that function just takes an object as a parameter - and you can transfer whatever to that function (BigText, Text, Code etc.). You actually also could give the function a decimal variable in which case the function would throw an exception (str as string would return NULL).
So now you also know how to transfer large strings to and from COM objects:
- To the COM object, you just transfer a BigText variable directly to an object parameter and cast it to a string.
- From the COM object to add the string return value to a BigText using ADDTEXT.
- You cannot use BigText as parameter to a by-ref (VAR) parameter in COM.
In my WebService consumer project I use the following code to test my WebService:
// Initialize Service
CodeUnitPicture service = new CodeUnitPicture();
service.UseDefaultCredentials = true;
// Set the Image for Item 1100
service.SetItemPicture("1100", encodeFromFile(@"c:\MandalayBay.jpg"));
// Get and show the Image for Item 1001
string p = "";
service.GetItemPicture("1001", ref p);
decodeToFile(p, @"c:\pic.jpg");
System.Diagnostics.Process.Start(@"c:\pic.jpg");
and BTW - the source code for the two functions in the base64 COM object are here:
public string encodeFromFile(string filename)
{
FileStream fs = File.OpenRead(filename);
BinaryReader br = new BinaryReader(fs);
int len = (int)fs.Length;
byte[] buffer = new byte[len];
br.Read(buffer, 0, len);
br.Close();
fs.Close();
return System.Convert.ToBase64String(buffer);
}
public void decodeToFile(object str, string filename)
{
FileStream fs = File.Create(filename);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(Convert.FromBase64String(str as string));
bw.Close();
fs.Close();
}
If you whish to download and try it out for yourself - you can download the sources here:
The two Visual Studio solutions can be downloaded from https://www.freddy.dk/VSDemo.zip (the base64 COM object and the VSDemo test project)
The NAV codeunit with the two functions above can be downloaded from https://www.freddy.dk/VSDemoObjects.fob.
Remember that after importing the CodeUnit you would have to expose it as a WebService in the WebService table:
And.... - remember to start the Web Service listener (if you are running with an unchanged Demo installation).
The code shown in this post comes with no warranty - and is only intended for showing how to do things. The code can be reused, changed and incorporated in any project without any further notice.
Comments or questions are welcome.
Enjoy
Freddy Kristiansen PM Architect
Microsoft Dynamics NAV
Comments
Anonymous
November 04, 2008
The comment has been removedAnonymous
November 04, 2008
The comment has been removedAnonymous
July 09, 2009
The comment has been removedAnonymous
July 10, 2009
The error says it all - NAV 2009 doesn't support the byte[] type from COM objects. It looks like you could use the encodefromfile function from this post - which would give you a string with the encoded file contents.Anonymous
December 03, 2009
The comment has been removedAnonymous
December 03, 2009
If you downloaded the VSDemo.zip - then the base64 project has a post build command which invokes RegAsm with the newly compiled assembly. This DLL needs to be on the ServiceTier and you need to use RegAsm to register the COM object. If you use C# on the clients you wouldn't have to register the DLL - you could either make a reference to the project or you could copy the class.Anonymous
December 03, 2009
Thanks your your quick response. The build process unfortunately errors out telling me that neither Input assembly base 62.dll nor dependable assembly could be found (translated to English). Furthermore it is telling me that regasm was terminated with code 100. Do you have any idea what I am doing wrong?Anonymous
December 03, 2009
You should be able to find out what reference isn't available by looking in Visual Studio under references or under the error list. I am assuming that you are running Visual Studio 2008 with SP1Anonymous
December 03, 2009
Actually I am using Visual Studio without SP1 - could that be the problem? The Error list unfortunately does not give me any additional information but "missing base 64.dll" I see the following references related to base 64 project:
- System
- System.core
- System.data
- System.Data.DataSetExtension
- System.Xml
- System.Xml.Linq
Anonymous
December 03, 2009
I don't know whether a missing SP1 is the problem, but the list you are referring to here are not references - they are using statements. You need to consult the Solution Explorer where you will find a list of references. The problem you are having seems very basic Visual Studio / C# - and of course you cannot register the bas64 assembly before you have built it.Anonymous
December 03, 2009
It works now after restarting my computer - actually I do not know why it did not work before but at least it is working now. Thanks a lot!Anonymous
December 03, 2009
Sounds like you had the COM object loaded in either the Classic or the RTC. You need to close down clients before being able to rebuild the library.Anonymous
December 03, 2009
Would it be possible to change the encodeFromFile function to work with files that are not stored locally but accessible via URL?Anonymous
December 04, 2009
Hmmm, I tried a couple of things like creating a function encodeFromUrl that looks like: public string encodeFromUrl(string url) { System.Net.WebClient wc = new System.Net.WebClient(); Stream data = wc.OpenRead(url); StreamReader reader = new StreamReader(data); string s = reader.ReadToEnd(); int len = (int)s.Length; byte[] buffer = new byte[len]; buffer = wc.DownloadData(url); data.Close(); reader.Close(); return System.Convert.ToBase64String(buffer); } The system actually imports something into NAV, unfortunately my Classic-Client is not able to show that image (what is reflected by that small "no access"-sign.) However, if I export that picture it looks like the original picture. If I import the same image using your encodeFromFile fucntion, everything works fine. Any idea? Thanks JutAnonymous
December 04, 2009
I just double checked, looking at that picture from RTC however works hmmm.Anonymous
December 04, 2009
Classic only supports few image types.Anonymous
May 02, 2010
The comment has been removedAnonymous
July 26, 2010
Thanks for the info. But what would happen if rather than a pic or a single field, I would like to pass over a whole record? Is it possible without having to specify each field one by one?Anonymous
August 12, 2011
Thanks for the inspiration on this Freddy. During my implementation I thought of another way to do this, without having to create or install the custom Base64-COM automation. I wrote a blog post about it on http://kfuglsang.comAnonymous
March 20, 2012
I rewrote your code using .NET interop instead. This is what I came up with: GetItemPicture(No : Code[20];VAR Picture : BigText)
CLEAR(Picture); Item.SETRANGE(Item."No.", No, No); IF (Item.FINDFIRST()) THEN BEGIN Item.CALCFIELDS(Item.Picture); // Get Temp FileName TempFile.CREATETEMPFILE; FileName := TempFile.NAME; TempFile.CLOSE; // Export picture to Temp File Item.Picture.EXPORT(FileName); Picture.ADDTEXT(DNConvert.ToBase64String(DNFile.ReadAllBytes(FileName))); // Erase Temp File FILE.ERASE(FileName); END Where: DNFile = System.IO.File DNConvert = System.Convert
Anonymous
June 11, 2014
you have declared an Automation variable as "base64" . can u specify what is subtype of this automation variable. Because it is showing "Unknown Automation Server.Unknown Class"..... Regards: MayankAnonymous
November 25, 2014
The comment has been removedAnonymous
May 16, 2017
hi Freddy I saw your blog now in that blog you gave .net folder throught that .net folder how i get a dll fie and how to import that dll file in automation server.- Anonymous
May 17, 2017
That is from NAV 2009 I think and would not be the way you do this today- Anonymous
May 17, 2017
The comment has been removed
- Anonymous
- Anonymous