Archive by Author

Mocking Session in ASP.NET with Moq and VB.NET

logo

It has been a few years since I have had the chance to explore the latest enhancements to ASP.NET. The development in my day job was locked into traditional ASP.NET forms just before MVC 1.0 was released and there has not been any opportunity to change. TDD, dependency injection and inversion of control containers all slipped by with me staring at them with my nose pressed against the window. I also develop in php with CodeIgniter, so I am quite familiar with the idea of MVC and was thoroughly excited when recently, the company I work for, began brewing the idea for a new project. Now was the time to change, so I stocked up on books and blog articles and began the big catch up. I won’t go on about how great MVC is, especially when I spent many hours battling against the web form paradigm – its such an anti-web way of doing things but with these new ways of working, I now feel a new vigour for .Net web development.

My journey found me working with Moq and although it is pretty self explanatory, most examples and articles are in C# and the documentation for the library is a little sparse. I don’t find the switching between C# and VB.NET very difficult as there is mostly only a syntax difference between them but I did find a problem when, in my unit tests, I needed to assert that entries in the session had been set correctly. So here is how to do it in VB.NET.

Checking a value has been set in Session

The first thing that you need is a session object that you can look at in your tests. It needs to inherit from HttpSessionStateBase and is pretty simple as the session is just a key-value store – we can use a generic dictionary to store the values we want to check:

Public Class HttpSessionMock
    Inherits HttpSessionStateBase
    Private ReadOnly objects As New Dictionary(Of String, Object)()
 
    Default Public Overloads Overrides Property Item(ByVal name As String) As Object
        Get
            Return If((objects.ContainsKey(name)), objects(name), Nothing)
        End Get
        Set(ByVal value As Object)
            objects(name) = value
        End Set
    End Property
 
    Public Overrides Sub Add(ByVal name As String, ByVal value As Object)
        objects.Add(name, value)
    End Sub
End Class

So we now have a session store and we can then use Moq to utilise that in our controller during our test. This is done be setting up a controller context:

 'Create a real mock session object to check value against
 Dim session As New HttpSessionMock()
 
 'Create a moq controller context
 Dim mockcontext As New Mock(Of ControllerContext)
 
 'Ensure our session object is returned when the session is requested
 mockcontext.SetupGet(Function(c As ControllerContext) c.HttpContext.Session).Returns(session)
 
 'set our controller context to be the moq context
 MyController.ControllerContext = mockcontext.Object

We can then run our controller action that sets a value in our mock session object and assert that the correct value is set as follows:

  'run the controller action
 Dim result = MyController.SomeAction()
 
 'assert the session contains the object we expect
 Assert.IsInstanceOfType(session.Item("SessionItemToCheck"), GetType(ExpectedSessionObject))
 
 'TODO:other tests to ensure session item is correct

Ensuring a value is returned from Session

You can use the above real mock session object to do this in exactly the same way but we can also setup Moq to return a particular value from session directly:

'create a test object/value we want session to return
Dim MySessionValue as New SomeObject()
 
'create our moq controller context
Dim mockcontext As New Mock(Of ControllerContext)
 
'ensure our test object gets returned by the moq session
mockcontext.SetupGet(Function(c As ControllerContext) c.HttpContext.Session("SessionItemToTest")).Returns(MySessionValue)
 
'assign our context to the controller	
controller.ControllerContext = mockcontext.Object
 
'TODO: run controller action and assert results

When I look at the code now, I realise that it is quite simple to achieve. However, it took me a little while to get my head around Moq and figure this out as all the examples out there seemed to be trying to do so much more than solely testing session state. It confused me anyhow.

AjaxQueue and jQuery 1.3

800px-Queue_algorithmn The website that I work on for the majority of my time had been running on the rather out of date jQuery 1.2 for quite some time. I recently found myself with a little bit of downtime and decided it was a good time to see if we could upgrade and take advantage of all the speed improvements that the latest version gives us.

On the most part, the upgrade was fairly painless. Most things worked without change but one particular plug-in did not – AjaxQueue. This is a great little control that acts as an add-on to the ajax method of jQuery, allowing a little more control over how concurrent calls are processed. It gives three modes of operation but for us, the most useful of these are abort and queue and from the names it is fairly obvious what they do.

After I upgraded to jQuery 1.3, it did not take me long to realise that the queue mode was broken. I briefly looked around for an alternative plug-in but found that all of them would require too much change to our existing controls. There was no alternative, I had to open up the hood of AjaxQueue and see what had gone wrong.

What I found was that there must of been a change to a method that the plug-in relied on – a method that it seemed, only became publicly available in version 1.3 – queue. Although the function was similar, the conclusion that I came to was that prior to version 1.3, the queue function automatically de-queued any function added and processed it. When using the latest jQuery, the ajax calls were queued but there was no code to start the processing of the queue.

It was slightly more complex I thought it would be to get it going – I had to ensure the queue was only started once. I finally came up with a solution and a newly revived plug-in, the code of which is below. As the original plugin has not been touched for quite some time (2007), I thought it ok to post my modified version here.

(function($) {
 
    var ajax = $.ajax,
        pendingRequests = {},
        synced = [],
        syncedData = [],
        ajaxRunning = [];
 
 
    $.ajax = function(settings) {
        // create settings for compatibility with ajaxSetup
        settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
 
        var port = settings.port;
 
        switch (settings.mode) {
            case "abort":
                if (pendingRequests[port]) {
                    pendingRequests[port].abort();
                }
                return pendingRequests[port] = ajax.apply(this, arguments);
            case "queue":
                var _old = settings.complete;
                settings.complete = function() {
                    if (_old) {
                        _old.apply(this, arguments);
                    }
                    if (jQuery([ajax]).queue("ajax" + port).length > 0) {
                        jQuery([ajax]).dequeue("ajax" + port);
                    } else {
                        ajaxRunning[port] = false;
                    }
                };
 
                jQuery([ajax]).queue("ajax" + port, function() {
                    ajax(settings);
                });
 
                if (jQuery([ajax]).queue("ajax" + port).length == 1 && !ajaxRunning[port]) {
                    ajaxRunning[port] = true;
                    jQuery([ajax]).dequeue("ajax" + port);
                }
 
                return;
            case "sync":
                var pos = synced.length;
 
                synced[pos] = {
                    error: settings.error,
                    success: settings.success,
                    complete: settings.complete,
                    done: false
                };
 
                syncedData[pos] = {
                    error: [],
                    success: [],
                    complete: []
                };
 
                settings.error = function() { syncedData[pos].error = arguments; };
                settings.success = function() { syncedData[pos].success = arguments; };
                settings.complete = function() {
                    syncedData[pos].complete = arguments;
                    synced[pos].done = true;
 
                    if (pos == 0 || !synced[pos - 1])
                        for (var i = pos; i < synced.length && synced[i].done; i++) {
                        if (synced[i].error) synced[i].error.apply(jQuery, syncedData[i].error);
                        if (synced[i].success) synced[i].success.apply(jQuery, syncedData[i].success);
                        if (synced[i].complete) synced[i].complete.apply(jQuery, syncedData[i].complete);
 
                        synced[i] = null;
                        syncedData[i] = null;
                    }
                };
        }
        return ajax.apply(this, arguments);
    };
 
})(jQuery);

Cuesheet Maker Bookmarklet for Discogs.com

Yesterday I discovered I had a few albums that had been ripped as a single track. I prefer having individual tracks and usually head on over to cuesheet heaven to look for the appropriate file that allows me to split them up with the awesome Medieval Cue Splitter. However I had a few tracks that had I could not find the appropriate cuesheet file for. I find myself in this situation fairly regularly and have also noticed that these albums usually have an entry at discogs.com, which has all the details including track lengths.

This got me thinking that it would be awesome to be able to create cue files from discogs.com. So I created the following – a bookmarklet to do just that.

Disclaimer: it is pretty rough and I cannot guarantee that it will work with all discogs.com entries and there is hardly any error checking. I have only used this in Firefox so I cannot guarantee it will work in any other browser. It only pops up a dialogue with the cuesheet contents on it but that was adequate for me to copy and paste it into a text file and tweak it so that it was a valid cuesheet.

The important thing is that it gathers the track names and artists and works out the index times. It also attempts to cope with double cds and also single tracks that are two different records playing at once (common with DJ mixes). Here is the bookmarklet (drag it into your bookmarks tookbar), followed by the code itself:

Drag this to your bookmark toolbar: Create Cue

The code itself:

javascript:(function(){var s=document.createElement('SCRIPT');
s.type='text/javascript';
s.src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js';
s.onload=function() {
	var title = jQuery.trim(jQuery('div.profile h1').text()),
		performer = jQuery.trim(title.substring(0,title.indexOf('-'))),
		file = title + '.mp3', secCount = 0, trkCount = 0, cues = [], cdNo = 1;
	title = jQuery.trim(title.substring(title.indexOf('-')+1));
	tracks = [];
	jQuery('div.tracklist .section_content > table tr').each(function(ix, el) {
		var trackCd = jQuery.trim(jQuery('td.track_pos',this).text());
		if (trackCd && trackCd != '') {
			var track = {}, tracktime = [], isDouble = false;
			trackCd = trackCd.split('-');
			if (trackCd.length > 1) {
				if (jQuery.trim(trackCd[0]) != cdNo) {
					createCue();
					cdNo = trackCd[0];
				} else {
					if (parseInt(trackCd[1]) == trkCount) {
						isDouble = true;
					}
				}
			}
			if (!isDouble) {
				trkCount+=1;
				track['trackno'] = trkCount;
				track['artist']= jQuery.trim(jQuery('td.track_artists',this).text()).replace(/\s-$/g, '').replace(/\n/g, '').replace(/\s+/g,' ');
				track['artist'] = jQuery.trim(track['artist']);
				track['artist'] = (track['artist'] == '') ? performer : track['artist'];
				track['title'] = jQuery.trim(jQuery('td.track_title',this).text()); 
				tracktime =jQuery.trim(jQuery('td.track_duration',this).text()).split(':'); 
				track['index'] = secondsToMinSec(secCount);
				secCount += (tracktime[0] * 60) + (tracktime[1] * 1);
				tracks[tracks.length] = track;	
			}
		}
 
	});
 
	createCue();
 
	//print it out
	var output = '';
	jQuery.each(cues,function(ix, cue) {
		output += cue + '\n\n\n';
	});
 
	alert(output);
 
	function createCue() {
		var quo = unescape('%22'),
			cue = 'TITLE '+quo+title+quo+'\n';
		cue += 'PERFORMER '+quo+performer+quo+'\n';
		cue += 'FILE '+quo+file+quo+' MP3\n';
		jQuery.each(tracks,function(ix, track) {
			cue += '  TRACK '+track.trackno+' AUDIO\n';
			cue += '    TITLE '+quo+track.title+quo+'\n';
			cue += '    PERFORMER '+quo+track.artist+quo+'\n';
			cue += '    INDEX 01 '+quo+track.index+quo+'\n';
		});
		cues[cues.length] = cue;
		//reset
		secCount = 0; trkCount = 0; tracks = [];
	}
 
	function secondsToMinSec(totsec) {
		var min = Math.floor(totsec / 60), 
			sec = (totsec - (min * 60));
		if (min < 10) {
			min = '0' + min.toString();
		}
		if (sec < 10) {
			sec = '0' + sec.toString();
		}
		return min.toString() + ':' + sec.toString() + ':00';
	}
};
document.getElementsByTagName('head')[0].appendChild(s);})();

I hope this helps some people, as it has me. Feel free to improve on it but if you make any changes, please let me know so that I can improve my version too.

Enjoy

Edit: Well it was not long until discogs changed their layout. I have update the code to reflect this. All should be well again.

IIS7 Wordpress and Friendly URLs

As a developer who uses Vista as their main OS, I have always found developing php sites a little bit awkward. Php never liked to play nicely with IIS and unless you installed and used Apache, friendly URLs were even more difficult to get working. I use IIS extensively for ASP.NET development and I never liked having both Apache and IIS installed together as they seemed to lock horns at every opportunity. My answer until now was to use VS.php, which runs an instance of Apache on demand, so I only have to use Apache when I need to. This was great for the most part but last week I had the situation where I was trying to work on both an ASP.NET project and a Wordpress project at the same time. Stopping and starting web servers every few minutes was getting a little dull.

rewriteThis spurred me on to take some time to see what all the fuss about IIS7 and php was really about. I was pleasantly surprised. Since Vista SP1, it is possible to configure IIS to run php using FastCGI. I won’t go into the detail here, there are plenty of examples on how to configure php in this way. Needless to say, it was not too difficult and I had a test phpinfo(); page displaying in no time. What’s particularly great is that Microsoft has been doing some great work getting php to run as quickly and efficiently as it can on IIS. If you are happy running php version 5.3, over at Php for Windows, you can pick up a version that has been compiled in Visual Studio 2008 which means faster and more stable than before.

The next step in my investigation was to see what could be done with the new url rewrite module for IIS. Another easy thing to install. What’s more, it has the ability to import .htaccess files. I use these extensively when using Apache and I already had one for the Wordpress site I was working on. Importing rules is a pleasure because of the neat interface that shows you which rules it can convert and which it cannot. Editing the original .htaccess directives on-screen allows you to fix those rules it is having trouble with. In my case it was just the RewriteBase, which I could happily remove without worrying about it.

One click of the ‘apply’ button and I was up add running. The site was responsive and low and behold, no issues with the friendly urls. Too easy!

Windows Server 2008, ASP.NET, Pre-compiling and Virtual Directories

windowsserver2008 There has been an interesting problem at work over the last couple of weeks. We are in the process of preparing to move service providers and at the same time upgrading all our servers to run Windows Server 2008 and SQL Server 2008. In preparation for this move, we have been upgrading our staging and test servers so we can be sure that everything will work as expected and to iron out the processes, so the move goes as smoothly as possible.

It took about ten minutes before we hit the strangest of problems. It was focused around the way we use a virtual directory to share common code and controls across different websites. The premise is that you create a project to hold your common javascript/images/ascx controls/web services and so on. You then use a virtual directory inside each of your websites that points to the common project. With only a small amount of tweaking for the common ascx controls everything seems to work without too much trouble. That is, until you try to do this on Windows Server 2008.

When we started up our web application on our new environment, we noticed almost immediately that the AJAX calls that used the common web service were failing. The error message was suggesting that the project that contained the common web service has not been compiled correctly. By copying the website across to a Windows 2003 server, we deduced that the problem was specific to 2008 as it worked perfectly on 2003. Having exhausted all the developers ideas on why this would be, we called Microsoft and got them involved in troubleshooting the issue.

A few days later an answer came: do not pre-compile your website. Sure enough, build the website without pre-compilation and it worked perfectly. Microsoft’s explanation was that it was due to a change in the way that Windows Server 2008/IIS7 worked. Fairly cryptic I must say and certainly does not leave me satisfied as to why it would not work and what have they done to stop it working. For us, using Windows Server 2008 and IIS7 is more important than the slight performance hit you get when deploying without pre-compilation. For others it may not be such a desirable solution.

Facebook-Style Expanding Textboxes With jQuery

Characters I was recently asked to create a textbox that would vertically expand depending on how much was written in it. This style of text box can be seen in the Facebook news feed for writing comments under peoples feed items. When I receive this type of request, I usually begin by researching what existing systems and plug-ins are out there, investigate the different approaches people take and then write my own based on which way I think best.

Firstly, a textbox (input type=text) is the wrong element to be using. It cannot be any larger than a single line, so a text area element needs to be used. As a text area element will not automatically expand by itself, the general approach to this problem is to use a hidden ‘staging’ element placed way off the viewable screen. The text entered into the text area is copied across to the staging element, which expands as needed and the resulting height used to resize the text area. It is not a difficult thing to achieve with jQuery and the basic structure of the plug-in can be made in relatively few lines of code.

There are a few things to watch out for however. Firstly, because we are taking the contents of a text area and placing it in a div, we need to watch out for special characters that need encoding before they will render correctly. Primarily the newline character but we also have to watch out for the other special characters that can cause issues. Nothing that cannot be fixed with regular expressions. The other thing that we need to be careful of is the CSS styles that have been applied to the text area. We require that the hidden element perfectly mimic the text area and that the text wraps at the same point. If it does not, we will incorrectly predict when the text area needs to grow. The font size and family, line height and padding need to be copied.

This would normally be the point that I post the code, but after I started writing the plug-in, I stumbled across an existing one that I could just not improve upon and decided to use it in it’s entirety. Its by a guy called Jason Frame and his plug-in can be found on github. For my particular implementation, I added the facility to have watermark text in it by utilising the great plugin over on digital bush and I also added some text parsing requirements that were required for the particular project.

Now for a word of warning. There are some problems with this approach, which I have not managed to solve (and incidentally, nor have Facebook). The text area element renders it’s content slightly differently in every browser. In the hidden element, the text will sit up directly against the edge of the div but in a text area it may not. I suspect this gap also varies from operating system to operating system but as far as I can tell, you cannot eliminate it with styles. The consequence of this is that predicting precisely when the text area needs resizing becomes very difficult. The only thing to do is to accommodate this margin of error. This can be done by making the text area display slightly taller than a single line and to always have an extra line available. That way, if things go slightly off, it is still usable. You can see this problem if you put a significant amount of dummy text into the text area and watch when it expands as you type. Instead of expanding when you reach a new line it happens at a different place. If any one manages to figure out how to completely eliminate this problem I am eager to hear how.

Thanks for Breaking Things ASP.NET 3.5 Team

broken_glass I have just spent an intense few hours crawling all the JavaScript files in our application (and there are a quite a few) fixing an issue introduced with our upgrade to the .NET framework 3.5. The entire afternoon in fact was spent fixing a single issue: our AJAX calls had stopped working.

The first hurdle was due to a slightly off-the-norm setup we have for our website. In order to save replicating code across the various websites that make up our solution, we opted for a common virtual directory that serves common assets like JavaScript, images and so on. With a little jiggery pokery, it also allows us to use shared .ascx controls. When we upgraded to the 3.5 framework, the upgrade wizard scans the projects and changes any references that should be pointing the new 3.5 versions. Not all references were changed, as the latest framework sits on top of the 2.0 framework, rather than replaces it. Unfortunately, the upgrade wizard could not figure out that our virtual directory project was not an application at all, so it created an unneeded web.config for it . However, removing that made little difference and it took me quite a while until I found that there were additional script handler settings added for framework 3.5 that were causing the errors. We chose jQuery as the backbone of our JavaScript development and only need to have the .ascx web service handler.

As soon as I removed the unwanted handlers, our AJAX calls sprung into life but still none of our AJAX powered controls worked. A quick look with Firebug revealed the reason. All our JSON responses where wrapped in a {d: } object. A little searching on the interweb and the reason was revealed. As a security measure, wrapping the response in that way can stop cross-site scripting attacks.

So why am I annoyed? Well, I cannot fault the reason behind the change, it stops a potentially nasty attack. However, we had no choice in the matter. It broke every one of our AJAX powered controls. Just because Microsoft had handled this change in their own AJAX controls, they decided that nobody else would be using the JSON serializer for anything else. As such, they seem to have no configuration setting to turn it off. We had been running the 2.0 AJAX extension with that vulnerability in it for a while, so I think we should at least be allowed to choose whether we continue that way. Instead, I have to spend hours checking all the JavaScript file for AJAX calls and making them look for their data inside the d object.

Do You Speak HTML?

bubble Ever since I have been living away from my home country of England, I have noticed myself picking up on what I would consider misuses of the English language. On the most part, they are Americanisms, for which I take great pleasure and amusement in refusing to understand, until the person uses the English pronunciation. However, I also seem to hear an awful lot of completely made-up words or incorrect grammar. One may argue that this does not matter, as long as you can communicate the message but I guess being from the country that shares it’s name with the language, I have a built-in pride for it’s correct usage.

In development, things are usually not so forgiving when it comes to language. If you don’t get the spelling or grammar correct, it just will not compile or operate as expected. That is, except for HTML. Browser rendering engines are not fussy at all when it comes to HTML and will render pages happily, even when the code is all over the place. There is historical reason for this but unfortunately it seems to have lead to complete complacency in developers.

HTML is not a difficult language, it is simple to understand, there are not that many grammar rules and the vocabulary is tiny. So why then is there such a massive quantity of web developers who are just not that fluent in the language? I am not talking about just being able to get a page to look like the design. I am talking about expressing the content of the page using correct elements and combinations there of. There is a reason behind every html element, they were created because of a need, and each should be used where it is appropriate to do so.

If you are from the school of thought that does not worry about this, as long as it renders as the designer imagined, then there are good reasons why you should be thinking otherwise. Every day, web bots will be viewing your pages and trying to understand them. Screen readers do the same thing and a badly constructed page can be totally confusing to a blind person. You could be losing out on valuable traffic because of this. You could also find yourself with more work down the line. A site like Css Zen Garden shows just how much can be done with design without touching the html. That is not so easy when you have to work with a badly created page.

So what if that is not a big enough reason for you? Well, look at it this way. People who can speak correctly and have an extensive grammar, find themselves in a much better position than those who don’t. It can give a great first impression. The same thing is true for development. You may consider yourself pretty good in your chosen programming language but if you are sloppy in such a simple language like html, it could be the deal clincher in a job interview. It would certainly change my impressions of a potential candidate if they don’t have a decent grip on something that they should be able to in their sleep.

So how is yours? Time to brush up perhaps? Are you really that fluent? Be honest with yourself. How well do you speak html?

Edit: To all those that have taken this opportunity to pick holes in my use of language in this article: I do not consider myself an expert in English, by any means. I make as many mistakes as the next person. I now live away from England and on a daily basis I hear non-English people use phrases and pronunciations I was taught are incorrect. I don’t try to preach the way I was taught or consider that way the only way it should be done. My point was that something like Html should not have such variance. There is a worldwide standard we are all supposed to adhere to and were it not such a forgiving markup language, things would be much better.

Check your PHP UTF-8 Checklist

I spent way too long this weekend on a problem that had such a simple solution. I guess this issue may have been a little to do with the fact that I use the CodeIgniter framework, which does so much of the hard work for you. it’s easy to get complaisant.

I have been working with text files that contain multi-byte characters and had previously ensured that my database and tables were setup for UTF-8 and that everything in codeigniter was correctly configured. Yet still I was getting invalid character errors on the database insert.

As the text files were of varying formats, including excel’s unicode csv format, I had already ensured that the reading of the text file also included conversion to UTF-8. Thanks to the script on Practical Web Ltd, I was attempting to detect the format of the files and converting them to UTF-8 on the fly. Yet still I was getting invalid character errors on the database insert.

I even ran through my code line by line and checked for any string manipulation I was doing using non-safe string functions. Yet still I was getting invalid character errors on the database insert.

If I had any decent amount of hair left, I would certainly have pulled it all out by the time I figured out what was wrong. I only discovered the answer by accident when I decided to remove the string manipulation altogether. As soon as I did that, it worked a treat. Had I discovered a bug in the multibyte string functions? No.

I had not checked the default encoding of mbstring.

So please, make sure it is on your check list of things to do when dealing with multi-byte strings. Set up the default correctly or religiously use the encoding parameter in the multi-byte string functions.

Even better, you could use the great checklist on nicknettleton.com (see below), which seems to cover everything.

I totally deserved the dunce hat.

Edit: Looks like the link on nicknettleton.com is no longer available (thanks @Les). A little digging around led me to the same checklist on php UTF-8 on another site.

Revisited: Tcpdf – Variable Height Table Rows With MultiCell

A few weeks ago I wrote an article about creating variable height table rows using tcpdf. It was a neat solution and I liked it, which was the reason I blogged about it in the first place. However, it turns out that ’sharing the love’ is not the only reason to blog about these things. It can also be a learning experience thanks to the commenting from you out there in the interwebs.

The particular comment in question, from mike, made me realise that there was a bit of a flaw in my neat idea – page breaks. Because I was first drawing the text, the automatic page breaks played havoc with the positioning when it came to drawing the borders. If the text was so long that it wrapped across enough lines to cause a page break, when I tried to reposition to draw the borders I was in completely the wrong place and it all went a little Pete Tong.

Still, not deterred by this little setback, I re-looked at the problem and have come up with a different approach that solves this problem and adds the ability to do a few more things.

When I relooked at the documentation, I discovered the useful little function: getNumLines(). This actually allows us to determine how many lines a string of text will take up, given a particular width. In effect, it allows us to do what I was using MultiCell to return, without actually drawing anything. This lets us to determined the maximum cell height with one line of code:

$linecount = max($pdf->getNumLines($row['cell1data'], 80),$pdf->getNumLines($row['cell2data'], 80),$pdf->getNumLines($row['cell3data'], 80));

Now that we know the cell height before we have drawn anything, we can use MultiCell() to draw the text and the borders at the same time.

This was not the end, however. My particular implementation was a grid of data that had borders between the column and not between the rows. All very pretty until that automatic page break happened and gave us a grid of data without a border at the bottom. To add insult to injury, the new page ended up with a grid of data with no border at the top!

To solve this, I needed to know if the row was going to be the first of the page, so I could draw the bottom border on the previous row and give the new row a top border. With the use of the page dimensions I could work this out:

$dimensions = $pdf->getPageDimensions();
$hasBorder = false; //flag for fringe case
 
foreach($data as $row) {
	$rowcount = 0;
 
	//work out the number of lines required
	$rowcount = max($pdf->getNumLines($row['cell1data'], 80),$pdf->getNumLines($row['cell2data'], 80),$pdf->getNumLines($row['cell3data'], 80));
 
	$startY = $pdf->GetY();
 
	if (($startY + $rowcount * 6) + $dimensions['bm'] > ($dimensions['hk'])) {
		//this row will cause a page break, draw the bottom border on previous row and give this a top border
		//we could force a page break and rewrite grid headings here
		if ($hasborder) {
			$hasborder = false;
		} else {
			$pdf->Cell(240,0,'','T'); //draw bottom border on previous row
			$pdf->Ln();
		}
		$borders = 'LTR';
	} elseif ((ceil($startY) + $rowcount * 6) + $dimensions['bm'] == floor($dimensions['hk'])) {
		//fringe case where this cell will just reach the page break
		//draw the cell with a bottom border as we cannot draw it otherwise
		$borders = 'LRB';	
		$hasborder = true; //stops the attempt to draw the bottom border on the next row
	} else {
		//normal cell
		$borders = 'LR';
	}
 
	//now draw it
	$pdf->MultiCell(80,$rowcount * 6,$row['cell1data'],$borders,'L',0,0);
	$pdf->MultiCell(80,$rowcount * 6,$row['cell2data'],$borders,'L',0,0);
	$pdf->MultiCell(80,$rowcount * 6,$row['cell3data'],$borders,'L',0,0);
 
	$pdf->Ln();
}
 
$pdf->Cell(240,0,'','T');  //last bottom border

Note that there is a fringe case that I came across. This was when the row would not cause a page break as it was exactly the height of the remaining space. Any attempt to draw the bottom border with a separate call would cause a page break. I solved this by ensuring in that particular case, the cell was drawn with the bottom border already.

What gives this method more power (apart from actually working) is that we know when there is going to be a new page and we could choose to force a new page and if we wanted. We could then re-plot the grid headings before creating the row. That way we can have the grid headings on every page.

I am determined to get a water-tight solution to this problem and I think this gets us closer. If you do find any problems with it, let me know and I will see if we can solve them. Thanks for your feedback.

Update: Bretton Eveleigh has written a class that encapsulates this and other helper methods into a format that makes creating tables with Tcpdf much easier. See the comments below for the link. Thanks Bretton.