Latest Posts

Sunday, 26 July 2009

Embedded images when exporting Devexpress XtraReport to HTML

I use Devexpress controls with my development projects and came across an issue in Xtrareports whereby when exporting a report to HTML, all the images are referenced as local file system resources which makes it impossible to assign the resulting HTML to the body of an email if it contains images.

The original issue is here if you are interested: http://www.devexpress.com/Support/Center/p/CS17889.aspx

Well I've needed to do this again so thought I'd look for a workaround. Here's what I came up with.

Add this code to the code behind file of the report you want to send as a HTML email.

Private _ImageNumber As Integer ' Counter for the images
Private _HTML As AlternateView ' Holds the View that will contain the completed HTML portion of the message
Private _Images As List(Of LinkedResource) ' Container for the embedded images

Public Function GetMail(ByVal ToAddress As String, ByVal FromAddress As String) As MailMessage

Dim MM As New MailMessage(FromAddress, ToAddress)

' Create the plain text portion of the mail for those who can't view HTML
Dim PlainView As AlternateView = AlternateView.CreateAlternateViewFromString(GetTextString, Nothing, "text/plain")
MM.AlternateViews.Add(PlainView)

' Add the HTML portion of the message
MM.AlternateViews.Add(GetHTMLMailView)

Return MM
End Function

''' <summary>
''' Export the current report as text
''' </summary>
Private Function GetTextString() As String
Using MS As New IO.MemoryStream
ExportToText(MS)
Dim B As Byte() = MS.ToArray()
MS.Flush()
Return System.Text.Encoding.UTF8.GetString(B)
End Using
End Function



''' <summary>
''' Get an alternateview object that contains the HTML portion of the mail (including embedded images)
''' </summary>
Private Function GetHTMLMailView() As AlternateView
Try
' Initialise variables to default
_ImageNumber = 1
_Images = New List(Of LinkedResource)

' Add event handlers that will be used to modify the HTML produced by the export
For Each B As Band In Bands
SetEvents(B, True)
Next

' Create a stream to export into
Using MS As New IO.MemoryStream

' Export to the stream
ExportToHtml(MS)

' Create the HTML view of the message
_HTML = AlternateView.CreateAlternateViewFromString(System.Text.Encoding.UTF8.GetString(MS.ToArray), Nothing, "text/html")

' Add the embedded images
_Images.ForEach(AddressOf _HTML.LinkedResources.Add)

Return _HTML
End Using
Finally
_Images = Nothing

' Remove any event handlers we added earlier
For Each B As Band In Bands
SetEvents(B, False)
Next
End Try
End Function



''' <summary>
''' Recursive function to find all XRPicturebox controls
''' </summary>
Private Sub SetEvents(ByVal C As XRControl, ByVal Attach As Boolean)
If C.HasChildren Then
For Each a As XRControl In C.Controls
If TypeOf (a) Is XRPictureBox Then
If Attach Then
AddHandler a.HtmlItemCreated, AddressOf HTMLImageCreated
Else
RemoveHandler a.HtmlItemCreated, AddressOf HTMLImageCreated
End If
Else
SetEvents(a, Attach)
End If
Next
End If
End Sub



''' <summary>
''' The event handler that will handle the custom HTML generation
''' </summary>
Private Sub HTMLImageCreated(ByVal sender As Object, ByVal e As HtmlEventArgs)
Dim Img = CType(sender, XRPictureBox)

' Exit if the XRPicturebox does not contain an image
If Img.Image Is Nothing Then Return


' Set the custom HTML
' Embedded images are referenced using 'cid:<ImageID>'
e.ContentCell.InnerHtml = String.Format("<img src='cid:img{0}'>", _ImageNumber)

' Write the current image to a stream
Dim MS = New MemoryStream
Img.Image.Save(MS, Drawing.Imaging.ImageFormat.Png)
MS.Position = 0

' Create an embedded image from the memorystream
Dim R As New LinkedResource(MS, "image/png")

' Set the image identifier to the Value we supplied in the custom HTML
R.ContentId = String.Format("img{0}", _ImageNumber)

' Add the embedded image into our placeholder collection
' We will add it to the message later as we haven't created that yet
_Images.Add(R)

' Increment the counter ready for the next image
_ImageNumber += 1
End Sub



Use the following code when you want to send the mail (including embedded images)

Dim rpt As New MyReport
Dim SC = New SmtpClient(Server, 25)
Dim MM = rpt.GetMail(ToAddress, FromAddress)
MM.Subject = "Test Mail"
SC.Send(MM) 

If you need to do this for more than one report then just create a base report containing the code and inherit any reports that require the functionality.

You can use a similar method if you want to do the same for barcodes or charts within the report.

Complete demo project can be downloaded here. Don't forget to use the ProjectConverter if you are using a different version of the Devexpress Suite.

Leave me a comment if you find this useful. :-)

7 comments:

  1. MelikeJul 31, 2009 03:01 AM
    That's great..
    I was thinking to use Microsoft.mshtml assembly reference to locate all img tags in exported html string and replace their source property one by one. But for this, I first had to save report to a temp directory so that I can locate report files. Also I had to distribute the dll to all my customers. So this would be an inefficient solution.
    Your solution is the one what I need. Thanks..:)
    Melike
    ReplyDelete
  2. KuffsJul 31, 2009 09:24 AM
    I'm happy that this has helped at least one other person :-)
    ReplyDelete
  3. AnonymousAug 27, 2009 05:20 AM
    What about pictures with an URL?
    ReplyDelete
  4. KuffsAug 27, 2009 05:53 AM
    You would use the ImageURL property of the XRPicturebox control if you wanted to link to images on a web site instead of embedding them into the email.
    ReplyDelete
  5. ClausApr 18, 2010 03:43 AM
    Thanks for your solution, it works perfect - except the images are always embedded full size. Therefore I extended your code with a scaling snipped grabbed from http://www.vb-helper.com/howto_net_image_resize.html. Then you can even specify the size of the images to send.
    ReplyDelete
  6. Gustavo MarzioniNov 19, 2010 01:48 PM
    Nice! 5 minutes and I got it running with XAF!
    ReplyDelete
  7. AnonymousJul 14, 2011 12:38 PM
    Is there a way to allow XRShape images as well?
    ReplyDelete