Creating Stand-Alone ColdFusion Templates With Embedded Images
This isn't as cool as perhaps it sounds, but it's kind of neat! For project Skin Spider, I had to add an age verification page. This is ONLY for the demo - I don't plan on using it in the final application. Because of this, I don't want images on it to have to be stored in the standard file system. This makes the page extremely modular so that I can just drop it in and not worry about taking it out later.
To make this happen, all you have to do is put a test on the page to see WHY you are hitting the page. If you are hitting it for the HTML, then return the HTML. If, however, you are hitting it for an image, return the binary image data. Once you have this set up, all you have to do is point any image source values right back at the page.
<!--- Param the URL variables. --->
<cfparam
name="URL.image"
type="string"
default=""
/>
<!--- Check to see if we have an image to return. --->
<cfif NOT CompareNoCase( URL.image, "background" )>
<!---
Set the image data. We have stored the image data as
Base64 encoded binary data. We did this by sending
the binary data to BinaryEncode() passing in Base64
as the encoding.
--->
<cfset strImageData = (
"R0lGODlhBAAEAIAAADMzMwAAACH5BAAHAP8ALAAA" &
"AAAEAAQAAAIFRI4WqgUAOw=="
) />
<!---
Set the content. Since we need to be very carefule
about returning proper data, be sure to RESET the
content buffer (reset="true").
Since we stored the data in Base64 format, when we
write it to the content stream, we have to decode
it first.
--->
<cfcontent
type="image/gif"
variable="#BinaryDecode( strImageData, 'Base64' )#"
reset="true"
/>
<cfelse>
<!--- We are hitting the page to get the HTML. --->
<cfoutput>
<html>
<head>
<title>Embedded Images Using ColdFusion</title>
</head>
<body
<!---
Notice the URL for the background image.
It's calling itself (index.cfm).
--->
background="#CGI.script_name#?image=background"
>
Isn't ColdFusion The Coolest?!?
</body>
</html>
</cfoutput>
</cfif>
Notice the URL for the background image? It points right back at the CGI.script_name value; right back at itself. The only difference is that it is sending a request for an image (image=background). This forces the page to return an image, not the HTML content.
Now, we are using the CFContent tag attribute "variable" to write the binary data. This is only available in ColdFusion MX 7. If you are using ColdFusion MX 6, it gets a bit more tricky. Now, I am not 100% sure on this, but I think you need to write the binary data to the page's byte output stream. You can get this buried under the page context. So, if you are running MX 6, you can replace the CFContent tag above with this code:
<!--- Get the Page Context. --->
<cfset objPageContext = GetPageContext() />
<!---
Get the page output stream. We want the BYTE output
stream, not the character output stream. Therefore we
need to call GetResponse() TWICE from the page context.
This will give us the byte stream????
--->
<cfset objResponse = objPageContext.GetResponse(
).GetResponse()
/>
<!---
From the page response, we need to get the output
stream. This will give us the Byte output stream to
which we can write binary data.
--->
<cfset objPageOutputStream = objResponse.GetOutputStream() />
<!---
Set the content type and reset the buffer. Then, without
any white space, we have to write the binary data to the
output stream. This WRITE method is a bit tricky. The
first argument is the binary data itself. The second
argument is the offset of the output buffer to read from.
Since we want to get ALL the binary data, start at the
begining of the buffer (offset zero). Then, the third
argument is the length of the binary byte array. In this
case, we want all of it, so return the LEN() of the
array.
Not 100% sure why Len() works... but it seems to.
--->
<cfcontent
type="image/gif"
reset="true"
/><cfset objPageOutputStream.Write(
BinaryDecode( strImageData, 'Base64' ),
JavaCast( "int",
0
),
JavaCast(
"int",
Len( BinaryDecode( strImageData, 'Base64' ) )
)
) />
<!--- Flush the output stream buffer. --->
<cfset objPageOutputStream.Flush() />
<!--- Close the output stream. --->
<cfset objPageOutputStream.Close() />
So, that's the MX 6 way of doing it. Not 100% how it all works. I kind of pieced it together until it worked. I don't want to give too much MISinformation, so I am not going to try an explain it too thoroughly.
Want to use code from this post? Check out the license.
Reader Comments
It appears by this article that you might know that answer to my CF question.
It appears that binaryEncode(binaryimage, Hex) is new with CF 7. However that function appears to be exactly what I need in order to write a binary image into an LDAP directory. The trouble is that my client only has CF 6.1 and doesn't have the funds to upgrade just yet.
Is there a way to get this functionality in 6.1 somehow? I haven't a clue. CF 6.1 has toBinary() and toBase64() but as I can't seem to find something like toHex().
Thanks for even considering answering me :)
Nice blog!
@Vince,
THat is strange. I could have sworn BinaryDecode() was available in MX6 (which is what I posted). But, indeed, according to the documentation, it is MX7 only. I wonder how I made that mistake??? (Not that I am infallible, but that I thought I had tested this on MX6).
MX 6 still has Java capabilities. You might want to check into that:
http://answers.yahoo.com/question/index?qid=20070308025513AAjRXwD
Sorry I don't have a better solution.
Thanks Ben for responding. It appears that I am still not getting it. I tried what I was doing on a cf7 server and while binaryencode() worked, it didn't produce the results I was hoping for.
Here is my trouble and again, if you don't have the time to answer I totally understand.
-----
I have been able to write a form that allows the updating of several text fields within the LDAP directory using cfldap. What I need now is to provide the ability to upload an image and store that within LDAP.
Here is where it breaks.
<!--- upload file --->
<cffile action="upload" filefield="seljpegPhoto" destination="#root#\update\upload" nameconflict="makeunique">
<!--- read file as binary --->
<cffile action="READBINARY" file="#root#\update\upload\#file.serverfile#" variable="BinaryImageContent">
<!--- Replace with new image --->
<cfldap action="modify"
DN="#qry_get_one_emp.distinguishedName#"
attributes="jpegPhoto=#BinaryImageContent#"
modifytype="replace"
server="#LDAPServer#"
username="#LDAPUsername#"
password="#LDAPPassword#">
The resulting error: "ByteArray objects cannot be converted to strings."
If I change the above to
attributes="jpegPhoto=#ToBinary(BinaryImageContent)#"
it is redundant and causes the same error.
I can update the field without error if I do this:
attributes="jpegPhoto=#ToBase64(BinaryImageContent)#" I have read somewhere that this is the way binary data is stored within the ldap directory.
This takes the update but it stores it as a text attribute while all other photos when viewing it with my ldap browser show as binary. I have read some non-ColdFusion blogs talking about flagging the update with a switch like "...;binary" or "::binary" but putting that at the end like this:
attributes="jpegPhoto=#ToBase64(BinaryImageContent)#;binary"
just inputs those additional characters in the field or causes an error.
Is there some switch I am missing or something else I need to do in order to let CFLDAP know this is binary and to allow the update? I have found NO information on how to upload a binary object into LDAP using CFLDAP.
Thanks for listening. I hope you have an answer as this is the third straight day of trying all kinds of crazy things to get this to work and I'm at my wits end! :) I'm grasping at the possibility that since you have worked with photos a bit, you might have some insight?
I ended up solving the problem by using cfhttp get to send the uploaded file name and location to an aspx page that a friend wrote (Thanks Peter Jacoby!)
Here are the details:
COLDFUSION CODE:
<cfif Trim(seljpegPhoto) neq "">
<cftry>
<!--- upload image --->
<cffile action="upload" filefield="seljpegPhoto" destination="#root#\update\upload" nameconflict="overwrite">
<!--- cfhttp call passes file name to update.aspx which updates the photo --->
<cfhttp url="#webroot#qry/update.aspx?CN=#qry_get_one_emp.cn#&photo=#file.serverfile#" method="GET"></cfhttp>
<cfcatch type = "Any">
<cfset err = "#err#<li>There was an LDAP error updating your image.</li><br>">
</cfcatch>
</cftry>
<!--- we print a 1 in update.aspx page if successful --->
<cfif IsDefined("cfhttp.fileContent") and Trim(cfhttp.fileContent) neq 1>
<cfset err = "#err#<li>There was an LDAP error updating your image.</li><br>">
</cfif>
</cfif>
UPDATE.ASPX CODE:
<% @Page Language="C#" Debug="true" CompilerOptions='/R:"c:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\System.DirectoryServices.dll"'%>
<%@ Import Namespace="System.DirectoryServices" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
//_LDAPSERVER_ is your server and _LDAPUSERNAME_ & _LDAPPASSWORD_ is a username/password that has rights to update the binary attribute
System.Drawing.Image imgInFile = System.Drawing.Image.FromFile(Server.MapPath("../update/upload/" + Request.QueryString["photo"]));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
imgInFile.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
DirectoryEntry personInfo = new DirectoryEntry("LDAP://_LDAPSERVER_/CN=" + Request.QueryString["CN"] + ",OU=NA,OU=Employees,O=Teleatlas,C=Global", "_LDAPUSERNAME_", "_LDAPPASSWORD_", AuthenticationTypes.None);
try
{
personInfo.Properties["jpegPhoto"][0] = ms.ToArray();
personInfo.CommitChanges();
Response.Write ("1");
}
catch
{
Response.Write ("ERROR: Cannot find employee " + Request.QueryString["CN"]);
ms.Close();
imgInFile = null;
Response.End();
}
ms.Close();
imgInFile = null;
}
</script>
My CF wish for added functionality? Image/binary upload:
<cfldap action="modify"
DN="#qry_get_one_emp.distinguishedName#"
attributes="jpegPhoto"
files="#file.serverfile#"
modifytype="replace"
server="#LDAPServer#"
username="#LDAPUsername#"
password="#LDAPPassword#">
Anyway, I thought I'd post my solution even if it wasn't a CF-Only fix.
@Vince,
Very interesting stuff. I like that you are leveraging two different technologies. I think people (myself especially) become very tunnel-visioned when it comes to CF. Good to know other stuff out there rocks as well (and that it doesn't have to be one or the other).