May 2008 Entries

Today I needed to create a UserControl to allow a user to select to view the current page from a list of enabled languages. To begin with I thought it would be a fairly easy task, by simply calling GetLanguageBranches() in the DataFactory class. However this ONLY returns the versions of the page that have been translated, which didn't suit me as our requirement was that ALL (enabled) languages should be displayed in the language selector, and if a language version doesn't exist for the specific page then the fallback language be used.

So next I tried looping through the results from EPiServer.DataAbstraction.LanguageBranch.ListEnabled(); This seemed to be just what I needed, so I set about looping through all the languages, displaying a hyperlink for each, with the Text being the name of the language. The NavigateUrl property was much trickier - why is nothing ever simple!

So next I set about trying to return a language specific version of a page for each of my enabled languages, at first I tried using:

EPiServer.DataFactory.Instance.GetPage(
new LanguageSelector( [Language code here] )).LinkURL 

This caused two problems, firstly this only works if the page exists in that language, with a LanguageNotFound exception thrown otherwise. So in the Catch block I tried:

EPiServer.DataFactory.Instance.GetPage(
LanguageSelector.FallBack( [Language code here] )).LinkURL 

Much to my annoyance however, it always rendered to the currently selected language. My first thought was EPiServer's TemplatePage or a ControlAdapter were causing this when they generate the "FriendlyUrl", however while debugging I realised that the LinkUrl  property of the PageData class had the incorrect language code as part of the query string. So it must use the current culture when generating the URL string, regardless of the actual page.

Enter Steve, who suggested I try setting the culture to that of the SPECIFIC language before getting a reference to the page and getting it's LinkUrl property, and bingo it worked! The only thing to remember is to make sure I set the culture back to the correct one at the end of the loop to make sure I didn't break anything further down the response.

Here's the code. I've not included the code listing for the LanguageItem class to keep the post short, but you should get the gist of what's going on...

if (!IsPostBack)
{
    //Create a list to hold LanguageItem's (which simply has 'Name'/'Url'/'ImageUrl' properties)
    List<LanguageItem> langs = new List<LanguageItem>();
    //Get the current culture as we'll need it later
    string currentCulture = EPiServer.Globalization.GlobalizationSettings.CultureLanguageCode;
    //Loop through every enabled language
    foreach (LanguageBranch lang in EPiServer.DataAbstraction.LanguageBranch.ListEnabled())
    {
        //Set the culture to the language of the link being constructed.
        EPiServer.Globalization.ContentLanguage.Instance.SetCulture(lang.LanguageID); 
        langs.Add(new LanguageItem(
        //The name of the language
        lang.Name,
        //Get the current page (but as we've set a new culture it will get the 
        //relevant lanaguage if it exists, or the fallback if doesn't.
        EPiServer.DataFactory.Instance.GetPage(CurrentPage.PageLink).LinkURL,
        //Finally generate the url to the Language's Image (e.g. a flag)
        string.Format("~/App_Themes/{0}/Images/Languages/{1}.gif", Page.Theme,  lang.LanguageID)));
    }
    //Finally set the culture back to the what it was at the beginning.
    EPiServer.Globalization.ContentLanguage.Instance.SetCulture(currentCulture); 
    //Set the LanguageItem list as the DataSource for the repeater and bind
    rLanguages.DataSource = langs;
    rLanguages.DataBind();
}

If anyone happens to know of a 'better' way of doing this please let me know... i spent ages trying to get it to work, and sadly EPiServer's documentation was non-existent. I also couldn't find any blogs/articles on how to do it, which is why i decided to share it with you all...


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

If you've ever tried to manipulate images in C# using GDI+ you'll almost certainly have come across the following error at some point:

a graphics object cannot be created from an image that has an indexed pixel format

I know I have, and it took a fair bit of effort to find the solution so I thought I'd share it.

Here's a quick background on Pixel Formats... In 'Non Indexed' images, each pixel represents ONE colour. So a pixel might have a value &h0000FF (Red). In 'Indexed' images each pixel value is an index to a so-called 'palette' or 'colour table'. So a pixel might have a value of 3, which means use the colour in palette entry #3 for that pixel.

For some reason GDI+ doesn't support editing Indexed images (that use the palette/colour table approach).

Here's a simple work around which, if the Pixel Format is Indexed, will create a new (non-indexed) Bitmap image from the original image instance

Image original = Image.FromFile(SourceImagePath);

switch (original.PixelFormat)
{
   case System.Drawing.Imaging.PixelFormat.Undefined:
   case System.Drawing.Imaging.PixelFormat.Format1bppIndexed:
   case System.Drawing.Imaging.PixelFormat.Format4bppIndexed:
   case System.Drawing.Imaging.PixelFormat.Format8bppIndexed:
   case System.Drawing.Imaging.PixelFormat.Format16bppGrayScale:
   case System.Drawing.Imaging.PixelFormat.Format16bppArgb1555:

       // Create a new BitMap object using original Image instance
       original = new Bitmap(original);
       break;
}

Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I've just been (struggling) to create a new theme for SharePoint but kept getting the following error:

A theme with the name "MY-THEME-HERE 1011" and version already exists on the server

It turns out that my ThemeID (and the name of the folder) were too long - how the error message above gives any indication of that is beyond be! Anyway all you need to do is make sure the ThemeID is a maximum of 8 characters. Once I shortened it everything worked fine.

I'm new to SharePoint so ignore me if this 'common knowledge' - I thought I'd share for any other newbies out there...


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I've been experimenting with writing my own C# URL Rewriter using a HttpModule. It's surprisingly simple as .NET kindly has the following (where newURL is the path to the actual file) :

HttpContext.RewritePath(string newURL)

So this allows a URL such as:

/my-friendly-url

to actually point to:

/pages/content.aspx?id=5

 

Problem:

All was going well, but some pages were giving the following error for no apparent reason:

Cannot use a leading '..' to exit above the top directory.

After some investigation I tracked the problem down to any asp.net HyperLink controls that had an ImageUrl property set. More specifically those starting with a '~/', if I removed the tilde the problem went away. Suffice to say I wasn't entirely happy about this 'workaround' so set about looking for an answer to why it was happening, and a better solution.

 

Cause:

After what seemed like hours of googling I came across a great article (link at the end of this post) on 4guysfromRolla.com. The problem turns out to be a bug in the HyperLink control, whereby it 'double-resolves' the ImageUrl property (basically it doesn't check if the path has already been resolved further down the stack and calls base.ResolveClientUrl(imageUrl) every time).

 

Solution:

Thankfully the solution is impressively simple through the use of a .NET ControlAdapter. If you've not come across them before, ControlAdapters let you hook into (and/or alter) the rendering process of any control, without having to the change the implementation of it in every page (I don't want to have to change all my <asp:HyperLink /> definitions throughout my solution).

So to fix the problem i'm going to create a ControlAdapter (for all browsers - typically you'd use them for Browser specific rendering differences) that will override the HyperLink's render method, crucially not calling base.ResolveClientUrl(imageUrl), instead leaving the ImageUrl value 'as-is'. (see below)

public class HyperLinkControlAdapter : ControlAdapter
    {
        protected override void Render(HtmlTextWriter writer)
        {
            HyperLink hyperlink = this.Control as HyperLink;
            if (hyperlink == null)
            {
                base.Render(writer);
                return;
            }

            // This code is copied from HyperLink.RenderContents (using
            // Reflector). References to "this" have been changed to
            // "hyperlink", and we have to render the begin and end tags.
            string imageUrl = hl.ImageUrl;
            if (imageUrl.Length > 0)
            {
                // Let the HyperLink render its begin tag
                hyperlink.RenderBeginTag(writer);

                Image image = new Image();

                // I think the next line is the bug. The URL gets
                // resolved here, but the Image.UrlResolved property
                // doesn't get set. So another attempt to resolve the
                // URL is made in Image.AddAttributesToRender. It's in
                // the callstack above that method that the exception
                // or improperly resolved URL happens.
                //image.ImageUrl = base.ResolveClientUrl(imageUrl);
                image.ImageUrl = imageUrl;

                imageUrl = hl.ToolTip;
                if (imageUrl.Length != 0)
                {
                    image.ToolTip = imageUrl;
                }

                imageUrl = hl.Text;
                if (imageUrl.Length != 0)
                {
                    image.AlternateText = imageUrl;
                }

                image.RenderControl(writer);

                // Wrap up by letting the HyperLink render its end tag
                hyperlink.RenderEndTag(writer);
            }
            else
            {
                // HyperLink.RenderContents handles a couple of other
                // cases if its ImageUrl property hasn't been set. We
                // delegate to that behavior here.
                base.Render(writer);
            }
        }
    }

Next we need to tell the web site to use the ControlAdapter, this is done by creating a .browser file in the App_Browsers folder:

<browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter controlType="System.Web.UI.WebControls.HyperLink"
                     adapterType="MyProject.HyperLinkControlAdapter" />
        </controlAdapters>
    </browser>
</browsers>

And that's it, no more bug!

Worryingly Microsoft were first told about the bug in November 2006, yet in January 2007 set it's status to 'Closed (won't fix)'. Thankfully there is a nice fix/work-around.

Click here to read the 4guysfromRolla.com article.


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

Creating a custom HttpHandler is fairly simple, all you need to do is implement the IHttpHandler interface.

public class MyHttpHandler : IHttpHandler

I recently needed to access the Session object from within my HttpHandler to check if a value existed, however the HttpContext.Session object was always null! After several minutes of pulling my hair out I discovered I simply needed to implement an additional 'marker' Interface. As I only needed Read Only access to the session object I used IReadOnlySessionState as follows:

public class MyHttpHandler : IHttpHandler,
System.Web.SessionState.IReadOnlySessionState

However if you need write access simply use IRequiresSessionState:

public class MyHttpHandler : IHttpHandler,
System.Web.SessionState.IRequiresSessionState


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I was recently trying to check if IIS's SMTP sever was working correctly. To help I turned on the logging, leaving the default "W3C Extended Log Format" selected. I sent the test email and checked the log - to say it was unhelpful is an understatement! I was wrongly assuming that "extended" format meant it would have more information than the other logging formats which didn't have "extended" in their name!

Enter Steve who suggested I change the format to Microsoft IIS Log File Format (shown in the image below). I did, and the log file was suddenly completely what I was expecting and I could straight away see what I needed (the 'TO' email address in this case).

IIS_Logging

The above principle also works for normal web site logging to!


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

If you've ever wanted to indent ListItems in an ASP.NET DropDownList you've probably come up against a brick wall if you use a simple space. It will indent in the actual HTML OK, but browsers will simply ignore the whitespace and render all the items inline.

The trick is to do the following to enter the space, not use the spacebar:

Hold down "ALT" while typing 0160

Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist