The need for bar codes never seems to go away — it just morphs.
In a 2008 post, I talked about different ways to render a chart yourself, if the native types offered weren’t what you wanted. But I concluded that it might be better not to let the report do the rendering, when you have an external alternative.
This is especially true when it comes to bar code images. Some bar codes can be expressed using custom fonts, which means the RDL can do all the work and it’s no sweat for you. But, in QRCode’s case, I don’t even think it’s even possible for the RDL to render the image natively no matter what you do.
I have QRCode requirements on a number of fronts right now. I found a number of ways to get a QRCode in a report on the web — you will too — but most of them seemed over-complicated, used an outside service, etc.
Others suggested the Google Chart API, which of course I have also suggested, in the same 2008 post, as the obvious, head-slapping alternative to complicating your life with a DIY approach. For bar codes, though, you’re stuck with using Google Chart’s Infographics — QRCodes are here — which, as you’ll see, is a deprecated API. Some people, including my employer, object to taking that chance.
Other posted suggestions led the way to open source, free libraries to use for this purpose.
- One is client side javascript library, and I suppose you could do a hack to get this to work directly into a report, but after looking at whether a div or an iframe would be respected when SSRS interprets HTML tags, which they aren’t, I didn’t want to think further about this.
- The other is a little .NET project, which you can use as a DLL.
Now the question becomes: how do we expose the library we choose to use?
While we can register custom DLLs for use by ReportServer, in my current environment that gets a bit messy. I didn’t relish explaining how to reference the DLL and write the embedded code functions to our various report authors.
After thinking it over, I decided the best path would be a little external web app, which a report could call to get a QRCode as an external image, just as it would have done when using the Google Chart API.
The advantage here, besides simplicity of the reference call in the RDL, is that other applications in our environment, which also might need a QRCode, could call the same web app to get one. My new functionality wouldn’t be restricted to reports.
With this potential advantage in mind, I decided that the web app could expose multiple APIs, with at least one tuned for use by RDLs.
1 – Return a web page using the javascript functionality,
containing only a QRCode.
This method is suitable for any application (usually but not always web based) that can embed an iframe.
The API is basically
…and your ASPX page body looks just like this:
<div id=”qrcode” style=”width:100px; height:100px;”>
</div>
</body>
The header of your ASPX page uses the appropriate script references, as specified by the camples that come with the library, plus a slightly-adapted function for makeCode(), which grabs the value to encode from the incoming request:
<script type=”text/javascript” src=”./scripts/qrcode.js”></script>
<script type=”text/javascript”>
function makeCode() {
var elText = “<%= Request.QueryString(“id”) %>”;
var qrcode = new QRCode(document.getElementById(“qrcode”), {
width: 100,
height: 100
});
qrcode.makeCode(elText);
}
</script>
… since this is a client-side approach, of course there’s no additional code-behind code in this ASPX page, just the embedded reference to the Request.QueryString in the javascript function above.
2 – Return an image instead of a page, using the same API as above.
Here’s what that would look like, if it isn’t obvious:
This second web page uses server-side functionality. It can be used the same way as the first one, but works even if you just want to get an image returned (you don’t want to use an iframe, js is not available, etc). In my case, I chose to return PNGs, but you could make this configurable using a second parameter on the URL.
None of the samples I saw for returning an image from an ASPX rather than a page exactly worked for me, so I’ll show you the code I used here. Notice that I’m using the value encoded as the name of the PNG returned. This may not always be practical, and it might make more sense to use a static name:
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Dim id As String = Request.QueryString(“id”)
If String.IsNullOrEmpty(id) Then
id = “test”
End If
Dim tempBMP As Bitmap = GenerateQRCode(id)
Response.Clear()
Response.Buffer = False
Response.AddHeader(“Content-Disposition”, _
String.Format(“inline; filename=” & Chr(34) & _
“{0}.png” & Chr(34), id))
Response.ContentType = “image/png”
tempBMP.Save(Response.OutputStream, _
System.Drawing.Imaging.ImageFormat.Png)
tempBMP.Dispose()
Response.Close()
Response.End()
End Sub
The GenerateQRCode function invoked above is basically taken from samples that come with the library, in my case. If you were generating a different type of image or using a different library your version would be different but, for the record and for completeness, here’s my version of the function. There is no other code in this web page.
Dim Encoder As _
New Gma.QrCodeNet.Encoding.QrEncoder( _
Gma.QrCodeNet.Encoding.ErrorCorrectionLevel.L)
Dim Code As Gma.QrCodeNet.Encoding.QrCode = Encoder.Encode(val)
Dim TempBMP As New Bitmap(Code.Matrix.Width, Code.Matrix.Height)
For X As Integer = 0 To Code.Matrix.Width – 1
For Y As Integer = 0 To Code.Matrix.Height – 1
If Code.Matrix.InternalArray(X, Y) Then
TempBMP.SetPixel(X, Y, Color.Black)
Else
TempBMP.SetPixel(X, Y, Color.White)
End If
Next
Next
Return TempBMP
End Function
3 – Return an image using a custom http handler.
This API is both performant and perfectly suited for use in SSRS. I’d never written a custom handler before, so I followed the directions here and simplified my life by allowing the handler to work for “http://<WebApp>/*.PNG”. Now an API simply uses an image object with an external URL.
By default, the image you get encodes the value of the filename you use for the PNG (filename stem only, I discard the path and the PNG extension).
However, this is a RESTful interface, and it’s really easy to provide more intelligence for particular needs. For example, you could register your handler to *.IMAGE, and then parse the path for the type of image to return (with a suitable default, if none is given by the request, of course).
In our case, we wanted inventory labels with QRCodes that would link directly to a jira item in a specific project for each physical item bearing such a label.
Rather than force the client (in this case, an RDL that is formatted to print our inventory labels) to bear the burden of fashioning the full jira URL, we could easily put that intelligence into the handler.
Here’s my entire handler, which as you can see is very similar to the webform approach above but requires a different method (in my testing) to successfully write the response:
Imports System.Drawing
Imports System.IO
Imports Microsoft.VisualBasic
‘ again, project references Gma.QrCodeNet.Encoding
Public Class QRCodeHandler
Implements IHttpHandler
Public ReadOnly Property IsReusable() As Boolean
Implements IHttpHandler.IsReusable
Get
Return True
End Get
End Property
Public Sub ProcessRequest(ByVal context As HttpContext)
Implements IHttpHandler.ProcessRequest
Dim req As String = context.Request.Path
Dim prefix As String = _
IIf(req.ToLower().Contains(“jira”), My.Settings.JiraURLPrefix, “”)
Dim id As String = _
System.IO.Path.GetFileNameWithoutExtension(req)
If String.IsNullOrEmpty(id) Then
id = “test”
End If
Dim tempBMP As Bitmap = GenerateQRCode(prefix & id)
Dim buffer As Byte() = GetBytesForImage(tempBMP)
context.Response.Clear()
context.Response.ContentType = “image/png”
context.Response.OutputStream.Write(buffer, 0, buffer.Length)
context.Response.End()
buffer = Nothing
tempBMP = Nothing
End Sub
‘ insert same function as the WebForm class above
Return TempBMP
End Function
Private Function GetBytesForImage(tmpBMP As Bitmap) As Byte()
Dim outStream As New System.IO.MemoryStream()
tmpBMP.Save(outStream, _
Return outStream.ToArray()
End Function
End Class
… and, as you can see, it would be easy to special-case or adapt this code for other needs besides creating a jira URL, using path components.
For a handler, you’ll also need to register the handler in your web.config and/or with IIS. How you do this depends on exactly how you’ve set up IIS and your web site, and I’m really a novice at this, so either read the docs and the directions here or whatever other source is suitable for your web app, or as a last resort, you can drop me a line and I’ll try to help you muddle through.