First Umbraco project

I've almost finished my first Umbraco project, which is probably the most awesome CMS I've ever seen.

In case you've been under a rock (which, after having rooted around this thing, I am starting to feel like), Umbraco is a brilliant, ingenious, pure-XML-powered .NET CMS - sorry to say, but much better than DotNetNuke, and fully validates with XHTML 1.1 due to it's XML nature. It supports nested master pages, Intellisense with Visual Studio, custom user controls, XSLT rendering, Web Services and Metaweblog, and a whole bunch of other shiny stuff (multiple host headers too, but I've yet to play around with this bit).

New link will be posted up shortly, but in the meantime I'll post up some code for a custom XSLT module for rendering either single images or iterating through a folder of media items. It takes a few parameters just for sensibility, but it's pretty easy to follow through and it works a treat - there's five places on the new site where I use this one block of code (80 lines of XSLT) to achieve four different effects - one link to files, one list of promotional image banners, one individual promotional banner, and four jQuery galleries (yes, off this one block of code) - and I suspect that it'll be a lifesaver for many future projects to come.

Without further ado, here's the code:

   1:  <?xml version="1.0" encoding="UTF-8"?>
   2:  <!DOCTYPE xsl:stylesheet [ &lt;!ENTITY nbsp "&#x00A0;"> ]>
   3:  <xsl:stylesheet 
   4:      version="1.0" 
   5:      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   6:      xmlns:msxml="urn:schemas-microsoft-com:xslt"
   7:      xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" 
   8:      exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">
   9:    <!-- Copyright Code Gecko Developments Ltd. 2009. All rights reserved.                  -->
  10:    <!-- Released under a BSD License (http://www.benjaminhowarth.com/home/ctl/terms.aspx)  -->
  11:    <!-- "Think free speech, not free beer" - Richard Stallman                              -->
  12:    <xsl:output method="xml" omit-xml-declaration="yes"/>
  13:    <xsl:param name="currentPage"/>
  14:    <xsl:param name="mediaID" select="macro/mediaID" />
  15:    <xsl:param name="separator" select="macro/separator" />
  16:    <xsl:param name="useBulleted" select="macro/useBulleted" />
  17:    <xsl:param name="useLinks" select="macro/useLinks" />
  18:    <xsl:param name="formatString" select="macro/formatString" />
  19:   
  20:    <xsl:template match="/">
  21:      <xsl:value-of select="formatString" disable-output-escaping="yes" />
  22:      <xsl:choose>
  23:        <xsl:when test="count(umbraco.library:GetMedia($mediaID/node/@id, 'true')/node) &gt; 1">
  24:          <xsl:choose>
  25:            <xsl:when test="string($useBulleted)='true'">
  26:              <ul>
  27:                <xsl:for-each select="umbraco.library:GetMedia($mediaID/node/@id, 'true')/node">
  28:                  <li>
  29:                    <xsl:apply-templates select="." />
  30:                  </li>
  31:                </xsl:for-each>
  32:              </ul>
  33:            </xsl:when>
  34:            <xsl:otherwise>
  35:              <xsl:for-each select="umbraco.library:GetMedia(macro/mediaID/node/@id, 'true')/node">
  36:                <xsl:apply-templates select="." />
  37:                <xsl:if test="position()!=last()">
  38:                  <xsl:value-of select="$separator" disable-output-escaping="yes"/>
  39:                </xsl:if>
  40:              </xsl:for-each>
  41:            </xsl:otherwise>
  42:          </xsl:choose>
  43:        </xsl:when>
  44:        <xsl:when test="count(umbraco.library:GetMedia($mediaID/node/@id, 'true')) = 1">
  45:          <xsl:apply-templates select="$mediaID/node" />
  46:        </xsl:when>
  47:        <xsl:otherwise>
  48:        </xsl:otherwise>
  49:      </xsl:choose>
  50:    </xsl:template>
  51:   
  52:    <xsl:template match="node">
  53:      <xsl:choose>
  54:        <xsl:when test="string($useLinks)='true'">
  55:          <a>
  56:            <xsl:attribute name="href">
  57:              <xsl:value-of select="data [@alias='umbracoFile']"/>
  58:            </xsl:attribute>
  59:            <xsl:call-template name="nodeContents" />
  60:          </a>
  61:        </xsl:when>
  62:        <xsl:otherwise>
  63:          <xsl:call-template name="nodeContents" />
  64:        </xsl:otherwise>
  65:      </xsl:choose>
  66:    </xsl:template>
  67:   
  68:    <xsl:template name="nodeContents">
  69:      <xsl:variable name="fileName"><xsl:value-of select="data [@alias = 'fileName']"/></xsl:variable>
  70:      <xsl:choose>
  71:        <xsl:when test="string($formatString)!=''">
  72:          <xsl:value-of select="umbraco.library:Replace($formatString, '{1}', @nodeName)" disable-output-escaping="yes" />
  73:        </xsl:when>
  74:        <xsl:otherwise>
  75:          <xsl:element name="img">
  76:            <xsl:attribute name="src"><xsl:value-of select="data [@alias = 'umbracoFile']"/></xsl:attribute>
  77:            <xsl:attribute name="alt"><xsl:value-of select="@nodeName"/></xsl:attribute>
  78:          </xsl:element>
  79:        </xsl:otherwise>
  80:      </xsl:choose>
  81:    </xsl:template>
  82:   
  83:  </xsl:stylesheet>

 

OK, so what've we got here... (for newbies, the bumpf of code above is called XSLT - eXtensible Stylesheet Markup Language).

Umbraco uses an entire XML-based system for all of it's content - even images.
This is great because then you can set up a hierarchical structure which your users can manage using the CMS administration system without having to worry as a developer about your beautiful folder structure - Umbraco handles all of that for you.

When executing an XSLT macro on an Umbraco page, you get a <macro>...</macro> XML document passed to the XSLT to be transformed. Umbraco lets you specify your own parameters to the macro, which get passed in as nodes with values.
In this instance, our parameters are mediaID, separator, useLinks, useBulleted and formatString (which all have their own purposes). This then turns into the following XML document to be thrown to the XSLT:

<macro>
    <mediaID>...</mediaID>
    <separator>...</separator>
    <useLinks>true/false</useLinks>
    <useBulleted>true/false</useBulleted>
    <formatString>&lt;span&gt;This is {1} file&lt;/span&gt;</formatString>
</macro>

 

So, our XSLT parameters pick up the macro/nodename parameters passed to the macro and then use them for determining the output of the XSLT. Sounds simple enough.
The one nuance with this macro is this - the mediaID field must be passed in as a Media Picker page field, you cannot specify a media item ID. However, this has it's benefits - by specifying a Media Picker item, it means you can specify a custom field on your page, which your users can then manage and edit themselves through the Umbraco admin interface, without having to do some horrible XSLT to obtain the node ID "behind the user's back". This way, your coding overhead is kept to a minimum (which means you can leave at 4pm instead of 5.30pm :-D).

So to call our macro from a master page is pretty simple:

<umbraco:Macro mediaID="[#mediaPicker]" useLinks="false" useBulleted="false" separator="&lt;br/&gt;" formatString="Download {1}" Alias="MediaRepeater" runat="server" />

 

Don't forget to set your parameters up in the admin system otherwise your macro will fail *cue Twitter fail-whale*.

Then, you specify your Media Picker page field in mediaID (note use of square brackets and # - do not attempt to use an <umbraco:Item> object here otherwise your macro will, again, fail), and hey presto - your users can manage the media objects loaded into your repeater.

I've used this to great success on a website where both a jQuery gallery rotator and a plain image repeater (promotional banner images) were required to be displayed. The same block of code powers both, but on the page where the gallery is required I've added my jQuery to the master page code, which allows me to re-use the above code for the promo images - and the client can look after their own promo images without knowing a single line of code.

A quick code run-through:
Lines 13-18 pick up the <macro><nodename> parameters.
Lines 20-50 specify the base template for the macro (match="/" - the "/" denotes the root node, check out W3Schools if you need to learn XPath) - some fancy formatting to a) see if you want a bulleted list in the output - the useBulleted parameter - and b) check if the specified media is a single file, or a folder of files to be iterated through.
Lines 52-66 test if you want each item to be rendered as a link (the useLinks parameter), then renders the individual item using the nodeContents template.
Lines 68-81 renders the item depending on whether formatString is specified or not - this is where you can customise the output of each rendered item. I only had need for two purposes - render an image, or some text linking to a file, hence the simplicity of this code. However, it's extensible and I'm releasing it under the BSD license cause I can see it's gonna be flipping useful. Might even start some Umbraco tutorials if I find the time.
Please note change of site T&Cs to incorporate BSD license into any code published via my blog.

Have fun!

CodeGecko

Database discovery and dynamic SQL in SQL Server 2005

I'm about to set up a new website, powered by DotNetNuke (my favourite CMS at the moment, mainly because it's free), and I want to install a Counter-Strike game server onto the same machine. I also want a little module that will show visitors to my site how many people are logged onto the CS server and some basic stats.
Sounds simple right?
The problem is that when Counter-Strike server is installed, the database name is generated at random. So I can't create a connection string that points to a data source because I don't know the name of the database. However, I do know what the CS tables look like.
In SQL Server, there's no way of obtaining all the table names in all the database just by running one query. So, I created a bit of code which:

  1. Gets all the databases on the server;
  2. Queries the INFORMATION_SCHEMA.tables table, which holds the names of all the tables in the database;
  3. Return the name of the database which contains the CS tables (in this case, cs_server).

The following piece of code does just that using dynamic SQL - SQL that writes itself, so to speak.

Bear in mind that when you run this, you must have SELECT access on all the databases on your DB server otherwise you will get "access denied" errors and the whole procedure will fail (so basically, run it as Windows Administrator or database sa).

Without further ado, here's the code:

   1:  CREATE PROCEDURE GetCounterStrikeServerDetails AS 
   2:  BEGIN  
   3:  CREATE TABLE #tmp ( 
   4:  tmpdbname nvarchar(MAX) 
   5:  ) 
   6:  DECLARE @useronline nvarchar(MAX) 
   7:  DECLARE @dbname nvarchar(255) 
   8:  DECLARE @csdb nvarchar(255) 
   9:  DECLARE @sql nvarchar(MAX) 
  10:  DECLARE dbcur CURSOR FOR 
  11:  (SELECT name FROM sys.databases) 
  12:  OPEN dbcur 
  13:  FETCH NEXT FROM dbcur INTO @dbname 
  14:  WHILE @@FETCH_STATUS = 0 
  15:  BEGIN
  16:   
  17:  SET @sql = 'IF (SELECT COUNT(*) FROM ' + @dbname + '.INFORMATION_SCHEMA.tables WHERE TABLE_NAME = ''cs_servers'') = 1 INSERT INTO #tmp SELECT ''' + @dbname + '''' 
  18:  EXEC(@sql) 
  19:  FETCH NEXT FROM dbcur INTO @dbname 
  20:  END 
  21:  CLOSE dbcur 
  22:  DEALLOCATE dbcur 
  23:  SET @csdb = CONVERT(nvarchar(max), (SELECT tmpdbname FROM #tmp)) 
  24:  DROP TABLE #tmp 
  25:  SET @useronline = 'SELECT name, cur_players, cur_map, maxplayers FROM ' + @csdb + '.dbo.cs_servers ' 
  26:  SET @useronline = @useronline + 'INNER JOIN ' + @csdb + '.dbo.cs_params ON ' + @csdb + '.dbo.cs_params.server_id = ' + @csdb + '.dbo.cs_servers.id' 
  27:  EXEC(@useronline) 
  28:  END 

 

 

 

Let's walk through this code.

Firstly, I declared a temp table (lines 3-5). Reason is because I had trouble with variable scope when trying to get the name of the database, which is on line 16.

Secondly, declare a number of variables to hold the data that I'll be working with (lines 6-9) - @useronline and @sql to hold my dynamically-generated SQL code, @dbname for my cursor to loop through each database, and @csdb for the name of the actual Counter-Strike database when I discover it.

Third, open a cursor, which loops through every database listed in sys.databases (lines 10-14).

Line 16 is the key. For each database, if I find an entry in INFORMATION_SCHEMA.tables which matches the name of the table I'm after (cs_server), I want to insert a row into my temp table with the name of the database.

Lines 20 and 21 are garbage collection for the cursor.
Lines 22 and 23 simply get the name of the CS database and put it into a variable, and dropping the temp table (garbage collection).
Lines 24 and 25 builds a SELECT JOIN statement using the CS database name and known table names for the CS database structure.
Line 26 executes said SELECT JOIN statement, which gives you the server name, max number of users allowed, current map, and number of users currently connected and playing.

If you take out lines 22-26, this code could be adapted for a number of uses - for example, in software asset management scenarios, this could be used to find particular pieces of software installed on a database server. I am running at least 3 DotNetNuke databases and if I know one of them has got a particular piece of software on it, and I want to delve into it, I'd be using this script.

I hope that someone finds it useful!

Benjamin

Today, we mourn a great loss.

Only spotted this today in the Telegraph's weekend Sport section.

IMG

'Nuff said :-D

CodeGecko

Tech toys

My Skype friend just bought this from Amazon.

He is now no longer my friend.
I want it!

CodeGecko

DotNetNuke 5 upgrade

SO I finally took the plunge and upgraded to DotNetNuke 5.00.01 beta on my live website.

Having create a staging site and then tested the upgrade in a protected environment, I discovered a few things had changed since version 4.9:

  1. Native jQuery support - YEEEESSS!!! A half-decent Javascript framework has been packed in - notice how the FancyZoom stuf on the portfolio page still works thanks to a great bit of code here;
  2. New container/skin architecture that inherits from the improved and re-organised DotNetNuke.UI namespace;
  3. Successfully breaking the administrative SolPartMenus for the blog module... lol.

Apart from that, upgrade went fairly smoothly There were some major hiccups, but these were caused by my failure to realise that I needed to upgrade Bruce Chapman’s iFinity Friendly URL Provider to the latest version. Skins have been updated too and a minimum of two sites will be going live this week!

Stay tuned,

CodeGecko

Pre-authentication and one-time passkeys (OTPs) using ASP.NET

I came across an interesting problem on Friday (which is why I'm blogging about it on Sunday evening - I've been working to fix this problem over the weekend!), regarding pre-authenticated links for secured areas of ASP.NET websites.

A very large and reputable client of our firm has an email sent out to them daily containing business-critical development information and a link to an online reporting feature. They requested if we could possibly arrange for the link to bypass the normal login system so that when a recipient of this email clicked the link, it would automatically authenticate them and take them straight to the appropriate reporting screens.

Now, our problem was that the site we've built doesn't make use of any of the four built-in ASP.NET Authentication mechanisms - Forms, Windows, Passport or custom. It made use of an underlying "SecureAdmin" class which inherited from System.Web.UI.Page, which then had underlying code to perform the authentication for each page we wanted to be "protected", and auth data was stored in Session - not the greatest way to do things. So I couldn't make use of the AuthenticationProvider.SetAuthCookie() method which would've enabled me to create a nice easy method to auth against with an SHA1 key.

However, I did get it working. I added a "secret" field to the Users table in our database, and this secret changed for all users, every time notification emails were sent out. The link included in the email then made use of the new key and attached it to the URL so pre-auth could be achieved without exposing usernames or passwords.

I would love to post code... but because of it's nature, I can't. However, I'm sure you get the idea for now, and I'll knock together a sample this evening to put up over the next couple of days.

CodeGecko

N.B.: I'm now looking at Forms authentication and the ASP.NET membership provider model for version 4 of this project. I'm sure I'll find lots more to blog about it along the way!

I officially hate SQL

Don't get me wrong, it's a powerful language, but sometimes it just hacks me off.

In case anyone is wondering where the rant is coming from, I'm trying to take two stored procedures and merge them. One gets a list, and the second gets child records of the first list. And trust me, it is more complicated than it sounds because of particular nuances in the SPs.

If I solve it, I'll let you know!

Benjamin

New webhosts

Hi everyone,

It's been a fair few weeks since I last wrote a blog post so I thought I'd scribble one together very quickly!

Things are going great at the moment. I work for a fantastic (and I truly mean fantastic) marketing firm in central Bristol as a software developer, helping with the development of their award-winning direct marketing and response tracking products! And they're getting me a company laptop - I feel important... *warm glow inside*!

I've just moved the site to a new server (fully dedicated) with Fasthosts for quite a good price too. Still not enough to move to Rackspace but that is planned in the forseeable future.

Projects - lots of things coming imminently! I'm starting to work on a lot of things involving umbraco which are all looking very interesting!
Oh, and I'm moving house on Friday too so I'll post pictures of my Christmas tree decorated in true young male bachelor style - silver and blue!

Benjamin
Code Gecko

Bytes Desktop Assistant - Feature Release

As you may or may not be aware, I am a moderator on the Bytes Technology Forums, overseeing classic ASP forums but kicking around other areas like HTML/CSS, JavaScript/AJAX, SQL Server, Access, and .NET forums - basically a bit of a jack-of-all trades.

Recently the mod team has been discussing a desktop assistant for drafting and composing posts from a background app that sits dormant in your status bar until you decide you want to use it.

My online colleague insertAlias (aka Curtis) has just finished the first feature release which can be downloaded from the project repository on CodePlex.

I've installed it successfully on my Vista Business laptop and it seems pretty cool. I like the way it minimises to the taskbar, although it only comes pre-shipped with one template for the Bytes forum. I do like, however, the option of multiple forums - as a member of various other online geek-hubs this is very handy!

The next release might incorporate a web service to download favourite templates from the Bytes site using an authenticated web service, but this is to be ironed out amongst the Bytes moderators.

Watch this space!

Benjamin

Accessible CSS and Javascript tricks

Hi guys,

Well, the portfolio page has changed a bit! I've added this really cool Javascript effect known as FancyZoom, and extended to work with Prototype and Scriptaculous.
As an aside, DotNetNuke has native compatibility with two Javascript/AJAX frameworks - Project Atlas (or ASP.NET AJAX) and Prototype, but can't be made to work with any other frameworks like Mootools, jQuery and so on.

Funky little way of saving space on the page with a nice UI effect. Only downside is no AJAX, but DNN works with Atlas and Prototype has it's own AJAX library, so extending it won't pose too big a problem.

To make it search-engine optimised, I've done a couple of little CSS tricks.
The first one is that the images themselves are background-images, not <img> tags.
The second one is that the text has a CSS text-indent property set to a minus value, so the text itself is shunted far off screen out of the user's view.
In this particular instance it's {text-indent: -9999px;}
However, when you turn CSS and Javascript off (think accessibility and search engine spiders), the background image isn't shown and you just get a plain-text link with the contents of the lightbox directly underneath it.

Just goes to show that functionality and accessibility can work hand-in-hand!

I have a host of new projects on at the moment so I will be regularly posting problems I come across and how to solve them!

Benjamin