Saving thumbnails in the original file format with C#

I tripped up on a strange quirk working with the Image and ImageFormat classes recently. The intention was simple – load an Image object from an existing graphic, generate a thumbnail, and save it out in the original format. The Image class in .NET includes a handy “RawFormat” property indicating the correct format to save out in. So far, so easy. Except the object that RawFormat was returning didn’t seem to match any supported ImageFormat, and the Guid was one character out. For example, when loading a JPEG, you got:

b96b3caa-0728-11d3-9d7b-0000f81ef32e

when the Guid for ImageFormat.Jpeg.Guid was in fact

b96b3cae-0728-11d3-9d7b-0000f81ef32e

It turns out that the “RawFormat” seems to change to an internal format the moment you start modifying the original image. So the simple trick is to save the value of the RawFormat property first, do your modifications, and then save out the image using the original RawFormat value.

Redirecting from Community Server to WordPress

Just a quick note – if you switch from Community Server to WordPress like I have, in order to keep your links working you can add a simple regex rewrite rule to IIS. I simply used the following:

^/james_crowley/archive/(.*).aspx$

http://www.jamescrowley.co.uk/$1/

and

^/james_crowley/(.*)$

http://www.jamescrowley.co.uk/$1

where /james_crowley/ was where my blog was installed previously (on weblogs.asp.net as it happens).

Automatically tracking outbound links in Google Analytics

Google Analytics supports a nifty feature called “Events”, which is designed to allow you to track non-pageview type events. This is particularly helpful if you have an AJAX type interface on which you want to gather statistics, but another use I’ve found handy is to track clicks on external links to other sites. If you’re using the asyncronous version of the tags (if not, why not), then you should have some code that uses the window._gaq variable. In order to track events aside from the initial page view, you simply need to call the following each time you want to record an event:

window._gaq.push(['_trackEvent','Event Category', 'Event Value']);

Using your favourite javascript library (mine are jQuery and Mootools), it’s easy to hook this up to automatically fire Google event tracking for any external hyperlinks on the site. We simply look for all a tags with href attributes that begin with “http://”. Then, if the href doesn’t contain our current hostname, then we assume it’s an external link. Obviously the logic could be adjusted for your particular needs!

Here’s the mootools version:

document.getElements('a[href^="http://"]').addEvent('click',function(link) {
  var href = link.target.href;
  if(href.indexOf(window.location.host) < 0) {
    window._gaq.push(['_trackEvent','Outbound Links', href]);
  }
});

Or for the jQuery fans amongst you, just swap the first line for:

$('a[href^="http://"]').bind('click',function(link) {

Now, after 24 hours or so, if you check out the "Event Tracking" page under "Content" in Google Analytics, you'll see an outbound link category listing all the external links clicked on the site (assuming Javascript is turned on, of course).

Side note: previous versions recommended by Google included a window.timeout before allowing the redirect to take place - in order to ensure the request to Google to record the click goes out first - as far as I can establish, this is no longer necessary.

Side note 2: By tracking outbound clicks this will affect your bounce rate figures. Essentially an event counts as another activity, so if a visitor lands on one page, and then clicks an external link, that will not count as a "bounce". Whether this is a fair reflection of bounces or not depends on your viewpoint - but something to bear in mind. Unfortunately there's currently no way to log an event that doesn't affect the bounce rate.

Google “panda” update – impact on small publishers

An open letter to Google.

I’ve been doing online publishing since 1999, across various successful websites, and have never knowingly been affected by any of your previous algorithm changes – sticking to the motto of great content, and optimizing for our user experience had so far done us well.

However, the Google “panda” algorithm changes being made over the last few weeks seem to have be far more wide reaching than any previously. I’m totally behind the goals you’ve stated to reduce the number of spam sites and content farms, but I’m sure I speak for a large number of publishers that we feel let down in the way this has been handled.

We are now consistently seeing exactly the kind of sites you claimed to want to punish, ripping off our content and ranking for our articles, while our original content is nowhere to be seen. I have talked to your PR team, contacted your search team, and all been given a standard response. We’ve posted in forums, filed reconsideration requests, and reported the people ripping our content off as spam. All with no effect.

I’ve no doubt that it was well within the technical realms of Google to establish which large, well established, high quality editorial sites would benefit and suffer – and so can only assume that a decision was made to press ahead regardless. While the really big publishers will no doubt survive, at the same time you are no doubt making or breaking thousands of small businesses across the web, who like it or not are dependent on traffic from Google, and yet have made every effort to produce genuine high quality original content for their audience.

Detecting 404 errors after a new site design

We recently re-designed Developer Fusion and as part of that we needed to ensure that any external links were not broken in the process. In order to monitor this, we used the awesome LogParser tool. All you need to do is open up a command prompt, navigate to the directory with your web site’s log files in, and run a query like this:

"c:\program files (x86)\log parser 2.2\logparser" "SELECT top 500 cs-uri-stem,COUNT(*) as Computed FROM u_ex*.log WHERE sc-status=404 GROUP BY cs-uri-stem order by COUNT(*) as Computed desc" -rtp:-1 > topMissingUrls.txt

And you’ve got a text file with the top 500 requested URLs that are returning 404. Simple!

Posting to Facebook Page using C# SDK from offline app

If you want to post to a facebook page using the Facebook Graph API and the Facebook C# SDK, from an “offline” app, there’s a few steps you should be aware of.

First, you need to get an access token that your windows service or app can permanently use. You can get this by visiting the following url (all on one line), replacing [ApiKey] with your applications Facebook API key.


http://www.facebook.com/login.php?api_key=[ApiKey]&connect_display=popup&v=1.0

&next=http://www.facebook.com/connect/login_success.html&cancel_url=http://www.facebook.com/connect/login_failure.html
&fbconnect=true&return_session=true&req_perms=publish_stream,offline_access,manage_pages&return_session=1
&sdk=joey&session_version=3

In the parameters of the URL you get redirected to, this will give you an access key. Note however, that this only gives you an access key to post to your own profile page. Next, you need to get a separate access key to post to the specific page you want to access. To do this, go to


https://graph.facebook.com/[YourUserId]/accounts?access_token=[AccessTokenFromAbove]

You can find your user id in the URL when you click on your profile image. On this page, you will then see a list of page IDs and corresponding access tokens for each facebook page. Using the appropriate pair,you can then use code like this:

var app = new Facebook.FacebookApp(_accessToken);
var parameters = new Dictionary
{
    { "message",  promotionInfo.TagLine },
    { "name" ,  promotionInfo.Title },
    { "description" ,  promotionInfo.Description },
    { "picture", promotionInfo.ImageUrl.ToString() },
    { "caption" ,  promotionInfo.TargetUrl.Host },
    { "link" ,  promotionInfo.TargetUrl.ToString() },
    { "type" , "link" },
};
app.Post(_targetId + "/feed", parameters);

And you’re done!

Applying app.config transformations (in the same way as web.config)

Visual Studio 2010 doesn’t have the same support for app.config files in the way that their web projects do, in order to vary connection strings and other configuration settings for different release modes – a real shame. You can vote on the issue here. In the meantime though, the ASP.NET team have a fix, detailed here.

All you need to do is save their custom targets file, add an imports tag immediately before the closing tag:

  ...
  <Import Project="$(MSBuildExtensionsPath)\Custom\TransformFiles.targets" />
</Project>

And add a TransformOnBuild metadata property to each config file you want transformed. So

<None Include="app.config" />

becomes

<None Include="app.config">
  <TransformOnBuild>true</TransformOnBuild>
</None>

(note you don’t need to do this on the configuration specific config files such as app.release.config). Then you can write your app.Release.config and similar files in the same way you do for web.config files. Sweet!

web.config transformations for NHibernate

If you’re trying to use web.config transformations in VS 2010 with nHibernate you might be hitting the same issue I’ve been getting the transformation to match the hibernate-configuration node. The reason is because you have to specify an xmlns="urn:nhibernate-configuration-2.2" attribute as part of the configuration:

<configuration>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="connection.connection_string">connection string</property>
      ...
    </session-factory>
  </hibernate-configuration>
</configuration>

To fix, just set the namespace on the target node in the transformation file like this:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"
               xmlns:hib="urn:nhibernate-configuration-2.2">
  <hib:hibernate-configuration>
    <hib:session-factory>
      <hib:property xdt:Transform="Replace" xdt:Locator="Match(name)" name="connection.connection_string">your connection string</hib:property>
    </hib:session-factory>
  </hib:hibernate-configuration>
</configuration>

Running OpenX under IIS with PHP 5.3 – date_default_timezone_get errors

I run OpenX (an open source ad serving platform) under IIS and PHP …. but after upgrading to PHP 5.3 noticed the following error appearing in the PHP error logs file (c:\windows\temp\php-errors.log by default).


PHP Warning: date_default_timezone_get(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'Europe/London' for '1.0/DST' instead in C:\inetpub\wwwroot\openx-2.8.5\www\delivery\spc.php on line 191

The solution to make this error go away is simply to specify a default timezone in the php.ini configuration file. In my case this looked like:

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = "Europe/London"

PHP 5.3 and IIS 7 – beware of MySQL issues with IPv6

I’ll keep this short and sweet as it’s covered in depth elsewhere. After installing PHP 5.3 suddenly my website stopped working – but instead of throwing errors, it simply sat there and eventually timed out (with no error). Turns out there are issues with both PHP and MySQL around IPv6. The most common solution is “turn off IPv6 support” – not a reassuring answer.

On the PHP front, the issue seems to be around fsockopen which works fine in 5.3.0 but not in 5.3.2. The major issue for most people though is that the MySQL driver doesn’t properly support IPv6 if you’re using a hostname (such as localhost) to connect instead of an IP address, particularly when localhost resolves to “::1″.

I’m not sure what changed in PHP 5.3.2 to bring this to the surface, but the solution is either to switch to using an IP address, for which there are other good reasons to do so, or modify your hosts file (c:\windows\system32\drivers\etc) and change the line the starts with “::1 localhost” to “#::1 localhost”.

Hope this helps someone!