Planet Claire

some say she's from Mars

Umbraco experiment - style related constants in XSLT

by Claire 10/30/2010 2:43:00 PM

The Problem - constants in XSLT files

I often find myself using constants in xslt, especially when using the fabulous ImageGen package. But as these constants are often directly related to the theme of the website, which is controlled by the css and encapsulated in a /style or /css directory in my project, it feels wrong to hard code these constants in xslt files.  In fact I have already been bitten on the bum on one site I look after, when a design makeover caused these constants to change, and not all of the values were contained in the stylesheet - messy.

Example - Feature boxes on home page

  • The site I'm working on at the moment has a 2 x 3 grid of feature photos with captions, which link to internal pages.
  • Each photo has a specific width which is tightly tied to the design of the website and will change if the design of the website changes
  • Usually, this would be controlled in the .css file, but
  • The site editor wants to be able to switch photos & captions at will, therefore we let them by making appropriate doctypes in umbraco
  • The site editor doesn't want to know about resizing images before uploading, therefore we use ImageGen
  • In the xslt which renders the feature pics, ImageGen lets us specify a width
  • I want that width to be stored in a central place inside the /style directory, for easier maintenance

Experimental Solution

I've got an idea of how to get around my problem, but as there are always so many ways to do things, I'm not sure if it's the best, so I'm going to run with it for a while & see how we go.

XSLT extensions seem to be the standard way to access C# code from XSLT.  One thing I discovered through experimentation is that your XSLT extension library doesn't have to be a separate class library to work, you can just give the namespace of the class inside your website project.  I guess that if XSLT extensions are meant to represent re-usable libraries then it makes sense for them to be a separate class library, but in this case I want to keep my class file of constants inside the /style folder (for maintainability).

1. Add a class to the styles directory containing your style related constants

Here's my class

namespace SCB.css
{
public class StyleConstants
{

public const int HomePageFeatureWidth = 208;

public int GetHomePageFeatureWidth()
{
return HomePageFeatureWidth;
}
}
}

2. Register the XSLT extension

In umbraco's /config/xsltExtensions.config file, add this

<XsltExtensions>
<ext assembly="SCB" type="SCB.css.StyleConstants" alias="styleConst" />
</XsltExtensions>

3. Use the constant in your XSLT

Here's my XSLT file that renders the feature pics, automatically sizing the photos my editor has uploaded to the correct width

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp "&#x00A0;">
]>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library" xmlns:styleConst="urn:styleConst"
exclude-result-prefixes="msxml umbraco.library styleConst">


<xsl:output method="xml" omit-xml-declaration="yes"/>

<xsl:param name="currentPage"/>

<xsl:template match="/">
<xsl:for-each select="$currentPage/picturesWithHeadingsLinks">

<xsl:variable name="item" select="umbraco.library:GetXmlNodeById(./content/id)"></xsl:variable>
<a href="{umbraco.library:NiceUrl($item/link)}">
<img src="{concat('/umbraco/ImageGen.ashx?image=', umbraco.library:GetMedia($item/picture, 0)/umbracoFile, '&amp;width=', styleConst:GetHomePageFeatureWidth())}" alt="{$item/heading}"></img>
<h2>
<xsl:value-of select="$item/heading"/>
</h2>
</a>
</xsl:for-each>


</xsl:template>

</xsl:stylesheet>

(Please excuse the crappy code rendering above, I really need to do some work on my own website)

Note the styleConst references above.  This effectively means that I can now style the home page using only the files inside the /styles directory.  When things change, I know I don't have to go scouting through XSLT files or elsewhere for hardcoded constants.

Tags:

Umbraco

Installing BlogEngine.NET into a sub directory of umbraco

by Claire 7/14/2010 8:37:00 AM

I've just spent the last several hours working out how to do this so will now blog it in the hope that it will save myself & anyone else out there some time, as there doesn't seem to be too much out there on this subject yet.  References yes, but nothing that told me exactly how to do this, so that's what I'm putting down here.

Motivation

I love Umbraco, it's brilliant.  And I want to use the packages available when I can.  The Blog4Umbraco package is fine for simple needs but for a more demanding client I have found that it's been a bit wanting in the following areas:

  1. Moderation of comments before the event.  For me this is essential even on a simple blog that allows comments, due to the massive volume of comment spam on the net now.  I think it's fair to say that comment spam has probably surpassed email spam in nuisance value, at least for anyone who runs a blog.  Even this tiny little blog, which runs on an earlier version of BlogEngine.NET suffers, I need to find the time to upgrade so I can use the promising new anti spam features of the latest BlogEngine.NET!  Time, my worst enemy!! 
  2. Categories.  Blog4Umbraco only has tags.  We want categories, each with their own rss feed so we can have one for a newsletter (which can be consumed by mailchimp for example), one for updates to our site, one for news from members, etc etc.  Each of these rss feeds can then be consumed in different ways & in different places.  Cool bananas!
  3. Importing of old posts & backdating of posts.  The fact that this is not do-able in Blog4Umbraco is a real pain in the bum.

I know that Blog4Umbraco is open source & that there is work being done to address these issues, I've looked into it myself and hope to contribute more at some point but unfortunately I just don't have the time at the moment and the client's needs come first.  I have plenty of experience with using BlogEngine.NET so I decided to have a go at using it inside an umbraco site so that we could have all the features of a blog together with Umbraco's wonderful CMS features.

Goals

These are my goals, they may not be the same as yours.

  1. The latest versions of Umbraco(4.5) & BlogEngine.NET (1.6.1), on IIS7, .NET 4.0 in integrated mode, using the default xml for the blog data.  This is what I've tested in, but I'm hoping it will work for slightly earlier versions of Umbraco too.
  2. Keep my source in source control.  For my umbraco projects, after setting up the website & installing the database, I always start a web application project in Visual Studio & copy the relevant directories from the umbraco website into it, adding Post Build events to copy my changes back into the website.  I believe this is common practice for many umbraco developers & enables me to keep my templates, css, xslt, user controls & other addons in source control.  If I'm going to run BlogEngine.NET then I want to take the same approach - the files that I'm going to be changing go into the webapp project & get copied over on build.
  3. The blog will live in a sub directory (say "/blog"!), and NOT be an application of it's own in IIS.  This is because I want to use umbraco macros in my BlogEngine master page.  In this way I can easily have common navigation for both the main site & the blog, and share css files too.  The fact that it won't be it's own application makes it slightly more complicated.
  4. Minimal changes to the BlogEngine source and minimal disruptions to the Umbraco base, we don't want to have nightmares when it comes to upgrade time.

Get it done

  1. Install your umbraco site & get it up & running.  Make a visual studio web application project for it & add some post build events to copy over the relevant directories to the website. So now you've got 2 locations - the website which IIS is serving, and your project directory which should be kept in source control.

  2. Make a new folder in the website, say /blog, and copy the contents of the BlogEngine.NET website into it.
  3. Move the 3 asp.net folders, App_Code, App_Data, App_GlobalResources to the root folder, merging with existing.
  4. Move the dlls from the blog/bin directory to the root bin directory.
  5. Move web.sitemap to the root directory
  6. Move global.asax to the root directory, open it up in a text editor & remove the Application_Error method.  This doesn't work well in IIS7 integrated mode.  I like to use Elmah for error logging in my apps anyway.
  7. Delete App_global.asax.dll from the root bin directory.  This is from umbraco but apparently isn't really necessary & is safe to remove.  It will interfere with the global.asax from blogengine, which is needed.
  8. copy /blog/web.config into the project directory & include in the visual studio project.
  9. copy /blog/themes/Standard/*.* into the project directory & include in the visual studio project.  You can rename this theme to your own at this point. 
  10. copy /App_GlobalResources into the project directory & include in the visual studio project.
  11. Add post build events to copy the files added in 8, 9 & 10 to the website directory on build.  Include the bin directory in your post build events if not already.
  12. Open up the root web.config in visual studio & cut the following web.config sections from the blog folder, paste them into the root web.config
    1. configSections/sectionGroup name="BlogEngine"
    2. BlogEngine
    3. connectionStrings if needed (not needed for XML, can delete)
    4. appSettings
    5. system.web/compilation/assemblies (if not already there)
    6. system.web/machineKey
    7. system.web/authentication (overwrite the umbraco forms element)
    8. the xml providers from membership, roles and all the sitemap providers.
  13. Delete httpModules & httpHandlers sections from both web.configs, they are not needed on IIS7 and deleting them will enable us to get more useful error messages from IIS
  14. Delete the system.webServer/validation section & change validateIntegratedModeConfiguration="true" in the root web.config.  This will give us better error messages.
  15. Change the BlogEngine.VirtualPath AppSetting to "~/blog/", or whatever directory name you're using.
  16. Add "~/blog" to the umbracoReservedUrls AppSetting.
  17. At this point the project will not build for a couple of reasons:
    1. The theme files need to be converted to the web application format.  Right click on the blog folder in VS & "Convert to Web Application".
    2. Add a reference to the BlogEngine.Core.dll that's in the website's root bin.
  18. The project should now build & you should confirm that the files were copied to the website directory
  19. There are some paths in the website project that don't use the BlogEngine.VirtualPath setting.  I changed them directly in the website folder, rather than importing them into the project and having to convert them to web application format.  This is just because I don't need to keep them in source control for any other reason & I'm trying to keep it simple.  They are:
    1. 34 occurrences of ~/admin/ in the blog/admin directory, in web.sitemap and in /widgets/Administration/widget.ascx.  Replace to ~/blog/admin.  (An easy way to do this is to open up a copy of the whole blogengine solution in visual studio & do a global replace, then copy the files back to your website folder)
  20. Check that everything works on the root site & on your blog.

One gotcha you might find along the way

After you add the blogengine httpModule called you might get an "unsupported form of compression" error.  Fix this by manually editing the App_Data/settings.xml file & switching off HttpCompression: - <enablehttpcompression>False</enablehttpcompression>.  Touch the web.config to restart the website.  (You can also switch this off via the blogengine admin panel but it's possible you might not have got as far as logging in yet).

Still to resolve

Sharing membership & role providers.  To login to blogengine you'll need to set the default providers in web.config to the xml providers, but to login to umbraco you'll need the original umbraco providers.  I haven't solved this problem yet but don't expect it to be a show stopper.  I'll update here when I have more.

Summary

At this point you should be up & going with your umbraco site still working in the root, & blogengine up & running in the blog folder.  Umbraco macros should work in the site.master for your blog theme, and you should be able to set your blog site.master to share the same css as your main site, so making the 2 sites look the same.

This is early days for me but I'm optimistic I can make it work.

References

http://www.danielbardi.com/blog/post/2009/10/07/Setting-up-BlogEngineNet-with-Umbraco-CMS.aspx
Various posts on the blogengine forum including http://blogengine.codeplex.com/Thread/View.aspx?ThreadId=51114

Tags: ,

BlogEngine.NET | Umbraco

Launching an umbraco website

by Claire 11/5/2009 5:59:00 AM

Here's a list of common tasks when launching a website, umbraco in particular, but a lot of these tasks are relevant to any asp.net website.

I've started this list for my own reference, it's not complete & not the last word either, but maybe it may help someone else.

1. Make sure you're in release mode

If you've got your relevant source files in a visual studio project then make sure that you build it in release mode. 

Also, ensure that debug="false" in the compilation element of web.config

The appsetting umbracoDebugMode is apparently allright to leave as "true" as it's not supposed to cause a performance hit.

2. Give the website full trust

Umbraco needs this in the web.config:


<system.web>
<trust level="Full" />
</system.web>

3. Configure a 404 page

Create a page in the website to handle 404 not found errors.  Add some navigation links to the home page, the search page or anywhere else that might be likely.  Add the node id of this page to the <errors404> element of /config/umbracoSettings.config

4. Configure a fallback error page

For any other unhandled exceptions that are not 404s you update the web.config like so:


<customErrors mode="On" defaultRedirect="~/error.aspx" />

5. Redirect the www subdomain to non-www or vice versa

Find out whether your client likes the wwws or not and then redirect accordingly, using the following syntax in /config/UrlRewriting.config.  Encourage your client to always use the preferred address in advertising and backlinks.


<add name="www" redirect="Domain" ignoreCase="true" rewriteUrlParameter="IncludeQueryStringForRewrite" virtualUrl="http://www.yourdomainname.com/(.*)" redirectMode="Permanent" destinationUrl="http://yourdomainname.com/$1" />

6. Redirect old pages.

If this is a site rewrite there are probably old pages to redirect.  It's important to issue 301 "permanent redirect" so that search engines will know the old pages have moved & not disappeared completely.  

Luckily umbraco makes this easy, it uses the urlrewriting.net project which can be used to make search engine friendly & people readable urls, but is also very useful for the important 301 redirects.  Simply add the relevant entries to /config/UrlRewriting.config file.  Here's an example:


<add name="about" virtualUrl="^~/index_files/Page344.htm" destinationUrl="~/about-us.aspx" redirectMode="Permanent" ignoreCase="true" />

Note that for the above example to work your site must either be running on IIS7 or you must map the .htm extension to ASP.NET under IIS6.  This is because the requested file is .htm which isn't handled by default under IIS6.  Ditto for image files & .pdfs.

Here's an example with a query string.  In this case the products had moved but google still had the old pages in it's index.  Also need to include rewriteOnlyVirtualUrls="false" to make this work as products.aspx is a real page.


  <urlrewritingnet xmlns="http://www.urlrewriting.net/schemas/config/2006/07" rewriteOnlyVirtualUrls="false" >
   
    <rewrites>
      <add name="resources" virtualUrl="^~/products.aspx\?idCategory=044c6017-34c3-4454-865f-d142c1776ab6" destinationUrl="~/products.aspx?idCategory=b02e9f0b-10f6-4ec8-b462-2a9d08410817" rewriteUrlParameter="IncludeQueryStringForRewrite" redirectMode="Permanent" ignoreCase="true"/>

If you don't have access to the old website you might not know which pages to redirect, so I often just google the domain name to find out which pages google's got in it's index & redirect those.

7. Add ELMAH error logging & notifications

You need this, really!  There are some good instructions on Lee Kelleher's site, my setup differs a little in that I choose to use the same SQL server database that umbraco lives in.  To do that, you need to run SQLServer.sql on your database.  My web.config entries are shown below.


  <configSections>
    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
    </sectionGroup>
  </configSections>
  <elmah>
    <security allowRemoteAccess="yes" />
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ElmahDB" />
    <errorMail from="websiteEmailAddress" to="yourEmailAddress" />
    <errorFilter>
      <test>
        <jscript>
          <expression>
            <![CDATA[
                    // @assembly mscorlib
                    // @assembly System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
                    // @import System.IO
                    // @import System.Web

                    HttpStatusCode == 404
                    || BaseException instanceof FileNotFoundException
                    || BaseException instanceof HttpRequestValidationException
                    /* Using RegExp below (see http://msdn.microsoft.com/en-us/library/h6e2eb7w.aspx) */
                    || Context.Request.UserAgent.match(/crawler/i)                     
                    /*|| Context.Request.ServerVariables['REMOTE_ADDR'] == '127.0.0.1' // IPv4 only*/
                    ]]>
          </expression>
        </jscript>
      </test>
    </errorFilter>
  </elmah>
  <connectionStrings>
    <add name="ElmahDB" connectionString="server=servername;database=db;user id=userid;password=pwd" />
  </connectionStrings>
  <system.web>
    <httpModules>
      <!--
                Uncomment the entry below if you are using MS Ajax v1.0.x.x
                and want to capture errors during Partial Postbacks
                NB This is not required for the version of Ajax shipped with .Net Framework v3.5!
      <add name="MsAjaxDeltaErrorLog" type="Elmah.MsAjaxDeltaErrorLogModule, Elmah"/>
            -->
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
      <!--
        Uncomment the entries below if error mail reporting
        and filtering is desired.
        -->
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
      <!--<add name="ErrorTweet" type="Elmah.ErrorTweetModule, Elmah"/>-->
    </httpModules>
    <httpHandlers>
      <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
    </httpHandlers>
    <authentication mode="Forms">
      <forms name="YourAuthCookie" loginUrl="umbraco/login.aspx" protection="All" path="/" />
    </authentication>
  <location path="elmah.axd">
    <system.web>
      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
  </location>

8. Give the login authorization cookie a unique name

In the web.config there's a section specifying what your login page is, if you're using forms authentication.  You should change the name of the cookie to something unique, otherwise you'll be sharing access with any other website which has the same cookie name. 

Incidentally you should also change the loginUrl to "umbraco/login.aspx" since that's the loginurl for umbraco.  Here's an example:


<forms name="AUTHWebsiteNameOrRandomString" loginUrl="umbraco/login.aspx" protection="All" path="/" />

You only need this section if you've got custom pages protected by forms authentication (such as elmah above).

9. Update the <mailSettings> element

Put some settings in here that are relevant to the production server, if indeed your website uses these settings, such as in a contact form.

10. Submit a sitemap to google

Not essential, but a good idea especially if it's a site rewrite.  Here are some suitable packages for umbraco, I use the Cultiv sitemap.

http://our.umbraco.org/projects/tag/sitemap#projectList

Submit the sitemap to google using google webmaster tools.

11. Google analytics. 

Add google analytics tracking code to the master template.  Set up an analytics user account for your client's email address.

12. Google maps

Don't forget to add your business to google maps, and embed the map on your website, if appropriate.

Tags:

Umbraco