TechSpoken

"Any ideas?" is the most frequently-asked question in technical forums. My answer is: yes.

In praise of offering multiple APIs: QRCodes for SSRS and more

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

http://<WebApp>/<MyURL>?id=[the value you want encoded in the QRCode goes here]

...and your ASPX page body looks just like this:

<body onload="makeCode() ;">
   <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/jquery.min.js"></script>
<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:

http://<WebApp>/<MySecondURL>?id=[the value you want encoded in the QRCode goes here]

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:

    ' Gma.QrCodeNet.Encoding is referenced in the project.

    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()
        tempBMP = Nothing
    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.

    Private Function GenerateQRCode(val As String) As Bitmap
        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.Web
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

    Private Function GenerateQRCode(val As String) As Bitmap
        ' 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, _
              System.Drawing.Imaging.ImageFormat.Png)
        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.