Using ScriptCS to very easily Base64 encode data in a file

Oh, what’s that? You need me to process a huge file and spew out the data plus its Base64? I’d better write a new .Net proje-OH NO WAIT A MINUTE I DON’T HAVE TO ANY MORE!

ScriptCS is C# from the command line; no project file, no visual studio (.. no intellisense..), but it’s perfect for little hacks like these.

First, go and install Chocoloatey package manager:

c:> @powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin

Then use chocolatey to install scriptcs:

cinst scriptcs

Then open your text editor of choice (I used SublimeText2 for this one) and write some basic C# plus references, save it as e.g. hasher.csx:
[csharp]
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;

var sourceFile = @"C:\Users\rposbo\import.txt";
var destinationFile = @"C:\Users\rposbo\export.txt";

var lines = File.ReadAllLines(sourceFile);
var newOutput = new List<string>();

foreach(var line in lines){
var hash = Convert.ToBase64String(Encoding.Default.GetBytes(line));
newOutput.Add(string.Format("{0},{1}",line,hash));
}

File.WriteAllLines(destinationFile, newOutput.ToArray());
[/csharp]

Then run that from the command line:

c:\Users\rposbo\> scriptcs hasher.csx

And you’ll end up with your output; minimal fuss and effort.

Content Control Using ASCX–Only UserControls With BatchCompile Turned Off

This is a bit of a painful one; I’ve inherited a “content control” system which is essentially a vast number of ascx files generated outside of the development team, outside of version control, and dumped directly onto the webservers. These did not have to be in the project because the site is configured with batch=”false”.

I had been given the requirement to implement dynamic content functionality within the controls.

These ascx files are referenced directly by a naming convention within a container aspx page to LoadControl(“~/content/somecontent.ascx”) and render within the usual surrounding master page. Although I managed to get this close to pulling them all into a document db and creating a basic CMS instead, unfortunately I found an even more basic method of using existing ascx files and allowing newer ones to have dynamic content.

An example content control might look something like:

[html]
<%@ Control %>
<div>
<ul>
<li>
<span>
<img src="http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
<a href="http://memegenerator.net/">Business Cat</a>
<span class="title">&#163;19.99</span>
</span>
</li>
<li>
<span>
<img src="http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
<a href="http://memegenerator.net/">Business Cat</a>
<span class="title">&#163;19.99</span>
</span>
</li>
<li>
<span>
<img src="http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
<a href="http://memegenerator.net/">Business Cat</a>
<span class="title">&#163;19.99</span>
</span>
</li>
</ul>
</div>
[/html]

One file, no ascx.cs (these are written outside of the development team, remember). There are a couple of thousand of them, so I couldn’t easily go through and edit them to all. How to now allow dynamic content to be injected with minimal change?

I started off with a basic little class to allow content injection to a user control:
[csharp]
public class Inject : System.Web.UI.UserControl
{
public DynamicContent Data { get; set; }
}
[/csharp]

and the class for the data itself:
[csharp]
public class DynamicContent
{
public string Greeting { get; set; }
public string Name { get; set; }
public DateTime Stamp { get; set; }
}
[/csharp]

Then how to allow data to be injected only into the new content files and leave the heaps of existing ones untouched (until I can complete the business case documentation for a CMS and get budget for it, that is)? This method should do it:
[csharp]
private System.Web.UI.Control RenderDataInjectionControl(string pathToControlToLoad, DynamicContent contentToInject)
{
var control = LoadControl(pathToControlToLoad);
var injectControl = control as Inject;

if (injectControl != null)
injectControl.Data = contentToInject;

return injectControl ?? control;
}
[/csharp]

Essentially, get the control, attempt to cast it to the Inject type, if the cast works inject the data and return the cast version of the control, else just return the uncast control.

Calling this with an old control would just render the old control without issues:
[csharp]const string contentToLoad = "~/LoadMeAtRunTime_static.ascx";
var contentToInject = new DynamicContent { Greeting = "Hello", Name = "Dave", Stamp = DateTime.Now };

containerDiv.Controls.Add(RenderDataInjectionControl(contentToLoad, contentToInject));
[/csharp]

232111_codecontrol_static

Now we can create a new control which can be created dynamically:
[html highlight=”1″]
<%@ Control CodeBehind="Inject.cs" Inherits="CodeControl_POC.Inject" %>
<div>
<%=Data.Greeting %>, <%=Data.Name %><br />
It’s now <%= Data.Stamp.ToString()%>
</div>

<div>
<ul>
<li>
<span>
<img src="http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
<a href="http://memegenerator.net/">Business Cat</a>
<span class="title">&#163;19.99</span>
</span>
</li>
<li>
<span>
<img src="http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
<a href="http://memegenerator.net/">Business Cat</a>
<span class="title">&#163;19.99</span>
</span>
</li>
<li>
<span>
<img src="http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
<a href="http://memegenerator.net/">Business Cat</a>
<span class="title">&#163;19.99</span>
</span>
</li>
</ul>
</div>
[/html]

The key here is the top line:

[html highlight=”1″]
<%@ Control CodeBehind="Inject.cs" Inherits="CodeControl_POC.Inject" %>
[/html]

Since this now defines the type of this control to be the same as our Inject class it gives us the same thing, but with a little injected dynamic content

[csharp]
const string contentToLoad = "~/LoadMeAtRunTime_dynamic.ascx";
var contentToInject = new DynamicContent { Greeting = "Hello", Name = "Dave", Stamp = DateTime.Now };

containerDiv.Controls.Add(RenderDataInjectionControl(contentToLoad, contentToInject));
[/csharp]

232111_codecontrol_dynamic

Just a little something to help work with legacy code until you can complete your study of which CMS to implement Smile

Comments welcomed.

A Quirk of Controls in ASP.Net

As part of the legacy codebase I’m working with at the moment I have recently been required to edit a product listing page to do something simple; add an extra link underneath each product.

 

Interestingly enough the product listing page is constructed as a collection of System.Web.UI.Controls, generating an HTML structure directly in C# which is then styled after being rendered completely flat.

 

For example:, each item in the listing could look a bit like this
[csharp]
public class CodeControl : Control
{
protected override void CreateChildControls()
{
AddSomeStuff();
}

private void AddSomeStuff()
{
var image = new Image
{
ImageUrl = "http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg",
Width = 250,
Height = 250
};
Controls.Add(image);

var hyperlink = new HyperLink { NavigateUrl = "http://memegenerator.net/", Text = "Business Cat" };
Controls.Add(hyperlink);

var title = new HtmlGenericControl();
title.Attributes.Add("class", "title");
title.InnerText = "£19.99";
Controls.Add(title);
}
}
[/csharp]
 

And then the code to render it would be something like:
[csharp]
private void PopulateContainerDiv()
{
var ul = new HtmlGenericControl("ul");

for (var i = 0; i < 10; i++)
{
// setup html nodes
var item = new CodeControl();
var li = new HtmlGenericControl("li");

// every 3rd li reset ul
if (i % 3 == 0) ul = new HtmlGenericControl("ul");

// add item to li
li.Controls.Add(item);

// add li to ul
ul.Controls.Add(li);

// add ul to div
containerDiv.Controls.Add(ul);
}
}
[/csharp]

The resulting HTML looks like:

[html]
<ul><li><img src="http://memegenerator.net/cache/instances/250×250/8/8904/9118489.jpg" style="height:250px;width:250px;" /><a href="http://memegenerator.net/">Business Cat</a><span class="title">&#163;19.99</span></li>
.. snip..
[/html]

And the page itself:

232111_codecontrol_blank_unstyled

I’ve never seen this approach before, but it does make sense; define the content, not the presentation. Then to make it look nicer we’ve got some css to arrange the list items and their content, something like:
[css]
ul { list-style:none; overflow: hidden; float: none; }
li { padding-bottom: 20px; float: left; }
a, .title { display: block; }
[/css]
Which results in the page looking a bit more like

232111_codecontrol_blank_styled

 

So that’s enough background on the existing page. I was (incorrectly, with hindsight, but that’s why we make mistakes right? How else would we learn? *ahem*..) attempting to implement a change that wrapped the contents of each li in a tag so that some jQuery could pick up the contents of that li and put them somewhere else on the page when a click was registered within the li.

So I did this:
[csharp highlight=”4,10,13″]
// setup html nodes
var item = new CodeControl();
var li = new HtmlGenericControl("li");
var form = new HtmlGenericControl("form");

// every 3rd li reset ul
if (i % 3 == 0) ul = new HtmlGenericControl("ul");

// add item to form
form.Controls.Add(item);

// add form to li
li.Controls.Add(form);

// add li to ul
ul.Controls.Add(li);

// add ul to div
containerDiv.Controls.Add(ul);
[/csharp]

I added in a <form> tag and put the control in there, then put the form in the li and the li in the ul. However, this resulted in the following HTML being rendered:

232111_codecontrol_elem_form

Eh? Why does the first <li> not have a <form> in there but the rest of them do? After loads of digging around my code and debugging I just tried something a bit random and changed it from a <form> to a <span>:
[csharp highlight=”4,10,13″]
// setup html nodes
var item = new CodeControl();
var li = new HtmlGenericControl("li");
var wrapper = new HtmlGenericControl("span");

// every 3rd li reset ul
if (i % 3 == 0) ul = new HtmlGenericControl("ul");

// add item to form
wrapper.Controls.Add(item);

// add form to li
li.Controls.Add(wrapper);

// add li to ul
ul.Controls.Add(li);

// add ul to div
containerDiv.Controls.Add(ul);
[/csharp]

Resulting in this HTML:

232111_codecontrol_elem_span

Wha? So if I use a <span> all is good and a <form> kills the first one? I don’t get it. I still don’t get it, and I’ve not had time to dig into it. in the end I just altered the jQuery to look for closest(‘span’) instead of closest(‘form’) and everything was peachy.

 

If anyone knows why this might happen, please do comment. It’s bugging me.

My first productionised powershell script

I’ve been tooling around with powershell recently, trying to teach myself some basics, and a recent support request which would have previously been done manually looked like a perfect opportunity for a little ps1 script.

The request was to disable a feature on the website which is configured from a setting in the web.config file on each server. Since web.configs are xml files, I thought I could treat it as such, traversing and editing values as needed.

So here it is; pretty lengthy for what it’s doing since I don’t know the nicer ways of doing some things (e.g., var foo = (bar == baz ? 0 : 1), and var sna = !sna), and as such any comments to help out would be appreciated:

[powershell]
function ValueToText([string] $val){
if ($val -eq "1"){return "enabled"}
else {return "disabled"}
}

[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
# pipe-delimited servers to work against
$servers = "192.168.0.1|192.168.0.2|192.168.0.3"

foreach ($server in $servers.Split("|")) {
write-host "Now configuring " $server

$file = "\\" + $server + "\d$\Web\web.config"
$xd.load($file)

# save a backup, just in case I snafu the site
$xd.save($file + ".bak")

# keys to edit
$nodelist = $xd.selectnodes("/configuration/appSettings/add[contains(@key,’Chat’)]")

foreach ($node in $nodelist) {
$key = $node.getAttribute("key")
$val = $node.getAttribute("value")
$setting = ValueToText($val)

$prompt = $key + " is currently " + $setting + ": toggle this? Y/N"
$toggle = read-host $prompt

if ($toggle -eq "Y" -or $toggle -eq "y"){
if ($val -eq "1") {$newbool = "0"}
else {$newbool = "1"}

$node.setAttribute("value", $newbool)

$newsetting = ValueToText($newbool)
$prompt = $key + " is now " + $newsetting
write-host $prompt
}
}
$xd.save($file)
}
write-host * done *
[/powershell]

It’s probably not much more than a “hello world”, but it certainly helped me out recently 🙂

Sony Xperia Mini X10 Pro: Corrupt Card “Fix”

So my phone regularly corrupts anything added to the SD card, even with various brand cards; all larger than the stock 2GB. I’d bought a 32GB one and though maybe it couldn’t handle that size (apparently it can) so went down to 16GB; still photos are corrupting as are podcast downloads, dammit!

Some people said it is linked to the phone not supporting certain class SD cards, some say certain brand, some certain size.

Bizarre fix that worked for me? Connect and mount your phone to your PC or use an IO File Manager app on the phone itself; open the two configuration xml files in the root.

Are they corrupted? Delete them and reboot – new ones are created and the phone works again.

Doubt this is in any way a long term solution, but it stops me throwing it out the window.

No warranties, it works for me, YMMV, etc.

Batch file to create an IIS7 website

Really simple stuff which is helping me out when hosting multiple sites for development on one machine; you either pass in as parameters or specify as responses

  1. the directory name of your site – e.g. “D:\Dev\MySite1” would be “MySite1”
  2. the port number you want it on
  3. the site ID

and it’ll set up the site, migrate your config settings to II7 if necessary, start the new site and let you know the URL to access it.

@echo off
setlocal EnableDelayedExpansion

REM Get parameters from user if they're not specified
if [%1]==[] set /P directory="Enter name of directory/site: "
if [%2]==[] set /P port="Enter port number: "
if [%3]==[] set /P sitenum="Enter site number: "

REM Create site in IIS
%systemroot%\system32\inetsrv\appcmd add site /name:"%directory%" /id:%sitenum% /physicalPath:"D:\Dev\%directory%" /bindings:http/*:%port%:%computername%

REM Attempt to migrate config to IIS7 stylee
%SystemRoot%\system32\inetsrv\appcmd migrate config "%directory%/"

REM Start new site
%SystemRoot%\system32\inetsrv\appcmd start site "%directory%"

echo site "%directory%" now running at http://%computername%:%port%

REM interactive mode
if [%1]==[] (if [%2]==[] (if [%3]==[] (
    pause
    exit
)))

I will change this to pull the next available site ID and port number unless someone else can tell me how to do that.

And yes, this would be very easy in Powershell but I’ve not done that version either..!

Also, if you’d like to know how I managed to get Syntaxhighlighter to work nicely with batch/cmd/dos, leave a comment. There are *no* nice, simple, tutorials out there with common mistakes, so I could paste my steps in here if necessary.

London Buses and The Javascript Geolocation API

The wonderful people at Transport For London (TFL) recently released (but didn’t seem to publicise) a new page on their site that would give you a countdown listing of buses due to arrive at any given stop in London.

This is the physical one (which only appears on some bus stops):

And this is the website one, as found at countdown.tfl.gov.uk

countdown

Before I continue with the technical blithering, I’d like quantify how useful this information is by way of a use case: you’re in a pub/bar/club, a little worse for wear, the tubes have stopped running, no cash for a cab, it’s raining, no jacket. You can see a bus stop from a window, but you’ve no idea how long you’d have to wait in the rain before your cheap ride home arrived. IF ONLY this information were freely available online so you can check if you have time for another drink/comfort break/say your goodbyes before a short stroll to hail the arriving transport.

With this in mind I decided to create a mobile friendly version of the page.

If you visit the tfl site (above) and fire up fiddler you can see that the request for stops near you hits one webservice which returns json data,

fiddler_tfl_countdown_1

and then when you select a stop there’s another call to another endpoint which returns json data for the buses due at that stop:

fiddler_tfl_countdown_2

Seems easy enough. However, the structure of the requests which follow on from a search for, say, the postcode starting with “W6” is a bit tricky:


http://countdown.tfl.gov.uk/markers/
swLat/51.481382896100975/
swLng/-0.263671875/
neLat/51.50874245880333/
neLng/-0.2197265625/
?_dc=1315778608026

That doesn’t say something easy like “the postcode W6”, does it? It says “these exact coordinates on the planet Earth”.

So how do I emulate that? Enter JAVASCRIPT’S NAVIGATOR.GEOLOCATION!

Have you ever visited a page or opened an app on your phone and saw a popup asking for your permission to share your location with the page/app? Something like:

Or in your browser:

image

This is quite possibly the app attempting to utilise the javascript geolocation API in order to try and work out your latitude and longitudinal position.

This information can be easily accessed by browsers which support the javascript navigator.geolocation API. Even though the API spec is only a year old, diveintohtml5 point out it’s actually currently supported on quite a few browsers, including the main mobile ones.

The lat and long can be gleaned from the method

[javascript]
navigator
.geolocation
.getCurrentPosition
[/javascript]

which just takes a callback function as a parameter passing a “position” object e.g.

[javascript]
navigator
.geolocation
.getCurrentPosition(showMap);

function show_map(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
// let’s show a map or do something interesting!
}
[/javascript]

Using something similar to this we can pad the single position to create a small area instead, which we pass to the first endpoint, retrieve a listing of bus stops within that area, allow the user to select one, pass that stop ID as a parameter to the second endpoint to retrieve a list of the buses due at that stop, and display them to the user.

My implementation is:

[javascript]
$(document).ready(function() {
// get lat long
if (navigator.geolocation){
navigator
.geolocation
.getCurrentPosition(function (position) {
getStopListingForLocation(
position.coords.latitude,
position.coords.longitude);
});
} else {
alert(‘could not get your location’);
}
});
[/javascript]

Where getStopListingForLocation is just

[javascript]
function getStopListingForLocation(lat, lng){
var swLat, swLng, neLat, neLng;
swLat = lat – 0.01;
swLng = lng – 0.01;
neLat = lat + 0.01;
neLng = lng + 0.01;

var endpoint =
‘http://countdown.tfl.gov.uk/markers’ +
‘/swLat/’ + swLat +
‘/swLng/’ + swLng +
‘/neLat/’ + neLat +
‘/neLng/’ + neLng + ‘/’;

$.ajax({
type: ‘POST’,
url: ‘Proxy.asmx/getMeTheDataFrom’,
data: "{‘here’:’"+endpoint+"’}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
displayStopListing(data.d);
}
});
}
[/javascript]

The only bit that had me confused for a while was forgetting that browsers don’t like cross browser ajax requests. The data will be returned and is visible in fiddler, but the javascript (or jQuery in my case) will give a very helpful “error” error.

As such, I created the World’s Simplest Proxy:

[csharp]
[System.Web.Script.Services.ScriptService]
public class Proxy: System.Web.Services.WebService
{

[WebMethod]
public string getMeTheDataFrom(string here)
{
using (var response = new System.Net.WebClient())
{
return response.DownloadString(here);
}
}
}
[/csharp]

All this does, quite obviously, is to forward a request and pass back the response, running on the server – where cross domain requests are just peachy.

Then I have a function to render the json response

[javascript]
function displayStopListing(stopListingData){
var data = $.parseJSON(stopListingData);
$.each(data.markers, function(i,item){
$("<li/>")
.text(item.name + ‘ (stop ‘ + item.stopIndicator + ‘) to ‘ + item.towards)
.attr("onclick", "getBusListingForStop(" + item.id + ")")
.attr("class", "stopListing")
.attr("id", item.id)
.appendTo("#stopListing");
});
}
[/javascript]

And then retrieve and display the bus listing

[javascript]
function getBusListingForStop(stopId){
var endpoint = ‘http://countdown.tfl.gov.uk/stopBoard/’ + stopId + ‘/’;

$("#" + stopId).attr("onclick","");

$.ajax({
type: ‘POST’,
url: ‘Proxy.asmx/getMeTheDataFrom’,
data: "{‘here’:’"+endpoint+"’}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) { displayBusListing(data.d, stopId); }
});
}

function displayBusListing(busListingData, stopId){
var data = $.parseJSON(busListingData);

$("<h2 />").text("Buses Due").appendTo("#" + stopId);

$.each(data.arrivals, function(i,item){

$("<span/>")
.text(item.estimatedWait)
.attr("class", "busListing time")
.appendTo("#" + stopId);

$("<span/>")
.text(item.routeName + ‘ to ‘ + item.destination)
.attr("class", "busListing info")
.appendTo("#" + stopId);

$("<br/>")
.appendTo("#" + stopId);
});
}
[/javascript]

(yes, my jQuery is pants. I’m working on it..)

These just need some very basic HTML to hold the output

[html]
<h1>Bus Stops Near You (tap one)</h1>
<ul id="stopListing"></ul>
[/html]

Which ends up looking like

The resultingfull HTML can be found here, the Most Basic Proxy Ever is basically listed above, but also in “full” here. If you want to see this in action head over to rposbo.apphb.com.

Next up – how this little page was pushed into the cloud in a few seconds with the wonder of AppHarbor and git.

UPDATE

Since creation of this “app” TFL have created a very nice mobile version of their own which is much nicer than my attempt! Bookmark it at m.countdown.tfl.gov.uk :


Quick and Dirty C# Recursive Find and Replace

Say you had a vast Visual Studio solution of something ridunculous like 120+ projects and wanted to test out a few proofs of concept on improving build times.

Now say that one of the proofs of concept was to use a shared bin folder for all projects in a single solution. Editing 120+ proj files is going to make you a little crazy.

How about a little recursive find-and-replace app using regular expressions (my saviour in many menial text manipulation tasks) to do it all for you? That’d be nice, wouldn’t it? That’s what I thought too. So I just did a quick and dirty console app to do just that.

[csharp]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.ObjectModel;

namespace RecursiveFindAndReplace
{
class Program
{
static void Main(string[] args)
{
// where to start your directory walk
var directoryToTraverse = @"C:\VisualStudio2010\Projects\TestSolutionWithLoadsOfProjectsInIt\";

// what files to open
var fileTypeToOpen = "*.csproj";

// what to look for
var patternToMatch = @"<OutputPath>bin\\[a-zA-Z]*\\</OutputPath>;";
var regExp = new Regex(patternToMatch);
// the new content
var patternToReplace = @"<OutputPath>;C:\bin\$(Configuration)\</OutputPath>";

// get all the files we want and loop through them
foreach (var file in GetFiles(directoryToTraverse, fileTypeToOpen))
{
// open, replace, overwrite
var contents = File.ReadAllText(file);
var newContent = regExp.Replace(contents, patternToReplace);
File.WriteAllText(file, newContent);
}
}

// recursive method to return the files we want in all sub dirs of the initial root
static List<string> GetFiles(string directoryPath, string extension)
{
var fileList = new List<string>();
foreach (var subDir in Directory.GetDirectories(directoryPath))
{
fileList.AddRange(GetFiles(subDir, extension));
}

fileList.AddRange(Directory.GetFiles(directoryPath, extension));

return fileList;
}

}
}[/csharp]

No doubt this could be made prettier with a little lambda, but like I said – quick and dirty.

—————–

Edit: I’ve just realised that Directory.GetFiles is inherently recursive. Duh. So the foreach instead becomes:

[csharp highlight=”4″]// get all the files we want and loop through them
foreach (var file in Directory.GetFiles(directoryToTraverse
,fileTypeToOpen
,SearchOption.AllDirectories))
{
// open, replace, overwrite
var contents = File.ReadAllText(file);
var newContent = regExp.Replace(contents, patternToReplace);
File.WriteAllText(file, newContent);
}[/csharp]

So that’s even quicker and slightly less dirty. Ah well.

Hackathon #1: Composite Image

My company recently had a Hackathon day, where the IT dept. was split into teams and had one day to develop something amazing to present to a panel of judges.

Over the next couple of posts I’d like to just show some things that my team hacked together, as I found them quite interesting, if not amazingly applicable to a real-world app!

First up is a very quick hack I created to generate a composite image on the fly given several other images. Apologies that this is in vb.net and not C#, but I was forced to work within our legacy environment! Assumption: all images to composite are of the same dimensions

Here is a function to take an arraylist of image URLs, merge them, and return the byte array:

[vb]Public Function BuildMergedImage(ByVal imgsToMerge As ArrayList) As Byte()

‘build image array, loading each image from a stream into the image array
Dim imagesToMerge As ArrayList = New ArrayList
Dim webClient As System.Net.WebClient = New System.Net.WebClient()
Dim imgStream As Stream

For Each imgToMerge As String In imgsToMerge
imgStream = webClient.OpenRead(imgToMerge.ToString())
imagesToMerge.Add(System.Drawing.Image.FromStream(imgStream))
Next

‘get composite dimensions
Dim height As Integer = CType(imagesToMerge(0), System.Drawing.Image).Height
Dim width As Integer = (CType(imagesToMerge(0), System.Drawing.Image).Width * imagesToMerge.Count())

‘init composite image
Dim mergedImage As Bitmap = New Bitmap(width, height)
Dim g As Graphics = Graphics.FromImage(mergedImage)

‘create composite image
For i As Integer = 0 To (imagesToMerge.Count() – 1)
g.DrawImage(imagesToMerge(i), (CType(imagesToMerge(i), System.Drawing.Image).Width * i), 0)
Next

‘save merged image to mem stream
Dim imgMemStream As New System.IO.MemoryStream
mergedImage.Save(imgMemStream, ImageFormat.Jpeg)

‘convert stream to byte array
Dim bytes(imgMemStream.Length() – 1) As Byte
imgMemStream.Position = 0
imgMemStream.Read(bytes, 0, bytes.Length)
imgMemStream.Close()

‘return byte array
Return bytes
End Function[/vb]

Then to use this method, build up a string arraylist of image urls, pass it to a new function, and output the returned byte array to the response buffer (I used mine in the Page Load, hence the obligatory postback check):

[vb]If Not IsPostBack Then
Response.Buffer = True
Response.ContentType = "image/jpeg" ‘match to the imageformat in the composite image function
Response.BinaryWrite(BuildMergedImage(taggedImg))
End If[/vb]

You’ll need these at the top of your file to get it working though. I realise the “option strict off” is a horrid hack, but it was for a hackathon..

[vb]Option Strict Off

Imports System.IO
Imports System.Drawing.Imaging[/vb]

Bit random, I know, but I found it quite interesting. Have a go with something similar to (but hopefully cleaner than) this:

[vb]Dim imageArrayList As ArrayList = new ArrayList

For Each imageUrl As String In Request.QueryString("i").Split(CChar("-"))
If Not IsNothing(imageUrl) AndAlso Not String.IsNullOrEmpty(imageUrl) Then
imageArrayList.Add(imageUrl)
End If
Next

If Not IsPostBack Then
Response.Buffer = True
Response.ContentType = "image/jpeg" ‘match to the imageformat in the composite image function
Response.BinaryWrite(BuildMergedImage(imageArrayList))
End If[/vb]

Example usage (although in this example it’s not pulling the correct dimensions through, which is odd):

[vb]http://localhost/combinedimages.aspx?i=http://www.docdatastorage.co.uk/Live/flyfiftythree/ProductImages/savage_white_1_medium.jpg-http://www.docdatastorage.co.uk/Live/flyfiftythree/ProductImages/checkit_green_1_medium.jpg-http://www.docdatastorage.co.uk/Live/flyfiftythree/ProductImages/telemaniacs_navy_1_medium.jpg[/vb]