Scott Hanselman

Internationalization and Classic ASP

July 24, '06 Comments [3] Posted in ASP.NET | Internationalization | XML | Bugs
Sponsored By

If you've ever done work in non-English languages with Classic ASP (ASP3) and gotten black squares in side of the characters you expected, your checklist should be something like this.

Remember: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

Classic ASP Internationalization "Don't Lie" Checklist

  • Are your ASP pages saved as UTF-8? I recommend Notepad2 (or debug.exe ;) ) as a good editor that knows what a Unicode Byte-Order-Mark looks like.
  • There's two aspects to encoding with Classic ASP - there's the encoding of the page (the static stuff) and the encoding of the dynamically created content.
    • Add this little-known bit-o-goodness to your pages:
      Response.CodePage = 65001
      Response.CharSet = "utf-8"
  • Make sure that the strings/content you are consuming is also the correct encoding. A very common problem is having Unicode content in an XML file but the prolog might say:
    <?xml version="1.0" encoding="iso-1159-1" ?>.
    This mistake will go unnoticed until José shows up.
    • Make sure your XML encoding matches you actual encoding:
      <?xml version="1.0" encoding="UTF-8" ?>.
  • You might also ensure your Http Headers don't lie:
    Content-Type: text/html; charset=utf-8
  • You might also ensure your META tags don't lie:
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

When all these things line up, things tend to just work. Again, this is old-school stuff, so you likely don't care. Move along, nothing to see here.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

XSLT with Powershell

July 24, '06 Comments [5] Posted in PowerShell | XML
Sponsored By

UPDATE: I've put a snapshot of this code at https://github.com/shanselman/nxslt2 as Oleg's properties have disappeared from the net.

I was talking to Chris at Portland Codecamp 2.0 yesterday and he was wondering if there were any XML cmdlets for XSLT in Powershell. There sure should be.

I wrote a few scripts with XsltCommand and System.Xml.Xsl stuff, but then thought it'd be easier and cleaner to use the rocking cool (and far more flexible) NXSLT2, by the very smart Oleg Tkachenko. One could right a bunch of cool XML cmdlets and get them shiny, wonderful and all integrated in a few days. Or, one could channel MacGyver and slap something together. Of course, you could just call nxslt from the command-line in the regular way:

nxslt2 file.xml file.xslt

But since one is always working with XML in powershell in variables, why not a little hack to make it easier:

PS>$a = [xml](get-content foo.xml)
PS>$b = transform-xslt $a foo.xslt

And $b now contains the transformed document as an instances of a [xml] type (System.Xml.XmlDocument).

Other things that could be done to make this easier/cleaner/more-PowerShelly would be to have it allow pipeline input, but I find the syntax confusing (within scripts) to make a parameter bind to pipeline input.

Thought (assuming this doesn't already exist): It'd be cool if one could say something line [param pipeline] in a function to indicate which parameter was the default for pipelining.
This is was 'filters' are for...the let functions get at the current pipeline via $_.

So, go download NXSLT2 and put it in somewhere in your PATH. Then dot-source this function in your Microsoft.Powershell_profile.ps1:

. transform-xslt.ps1

...where transform-xslt is

UPDATE: Here's a better version with some changes suggested by Peter Wong. Note that it's a filter not a function so it supports pipelining using the $_ variable:

filter transform-xslt
{
 param ( [string]$xsltpath =$(read-host "Please specify the path to an XSLT") )
 $PRIVATE:tempString = $_
 if ($PRIVATE:tempString -is [System.String])
 {
  $PRIVATE:tempString = [xml]$PRIVATE:tempString
 }
 if ($_ -is [xml])
 {
  $PRIVATE:tempString = ([xml]$_).get_outerXml()
 }
 [xml]($PRIVATE:tempString | nxslt2 - $xsltpath)
}

...and DO improve it (as it's crap now), and post your improvements here!

UPDATE#2: Keith Hill has a great article on making functions/filters that behave like truly integrated cmdlets. The difference between a Cmdlets (typically written in .NET and compiled) and functions is the level of integration with the intrinsic cmdlets. If you can't tell the difference between your new behavior/function/cmdlet and the built-in ones, you've succeeded. Here is his version that supports piped in objects as well as arrays of input files.

function Transform-Xml {
  param([string]$stylesheetPath=$(throw '$stylesheetPath is required'),
        [string[]]$xmlPath)
      
  begin {
    function applyStylesheetToXml([xml]$xml) {
      $result = $xml.get_OuterXml() | nxslt2.exe - $stylesheetPath
      [string]::join([environment]::newline, $result)
    }
    function applyStylesheetToXmlFile($sourcePath) {
      $rpath = resolve-path $sourcePath
      $result = nxslt2.exe $rpath $stylesheetPath
      [string]::join([environment]::newline, $result)
    }
  }
 
  process {
    if ($_) {
      if ($_ -is [xml]) {
        applyStylesheetToXml $_
      }
      elseif ($_ -is [IO.FileInfo]) {
        applyStylesheetToXmlFile $_.FullName
      }
      elseif ($_ -is [string]) {
        if (test-path -type Leaf $_) {
            applyStylesheetToXmlFile $_
        }
        else {
            applyStylesheetToXml $_
        }
      }
      else {
        throw "Pipeline input type must be one of: [xml], [string] or [IO.FileInfo]"
      }
    }
  }
    
  end {
    if ($xmlPath) {
      foreach ($path in $xmlPath) {
        applyStylesheetToXmlFile $path
      }
    }
  }
}

Here's a simple example of usage:

PS[1] C:\Documents and Settings\shanselm\Desktop\xslt
> get-content foo.xml
<company>
        <name>XYZ Inc.</name>
        <address1>One Abc Way</address1>
        <address2>Some avenue</address2>
        <city>Tech city</city>
        <coun\try>Neverland</country>
</company>

PS[2] C:\Documents and Settings\shanselm\Desktop\xslt
> Get-Content foo.xslt
<xsl:stylesheet
      xmlns:xsl="
http://www.w3.org/1999/XSL/Transform"
      version="1.0">
<xsl:output method="xml" />
        <xsl:template match="company">
                <poo>
                        <c><xsl:value-of select="/company/name"/></c>
                        <a1><xsl:value-of select="/company/address1"/></a1>
                        <a2><xsl:value-of select="/company/address2"/></a2>
                        <c1><xsl:value-of select="/company/city"/></c1>
                        <c2><xsl:value-of select="/company/country"/></c2>
                </poo>
        </xsl:template>
</xsl:stylesheet>

PS[3] C:\Documents and Settings\shanselm\Desktop\xslt
> $a = (get-content foo.xml) -as [xml]
PS[4] C:\Documents and Settings\shanselm\Desktop\xslt
>
$b = transform-xslt $a foo.xslt
PS[5] C:\Documents and Settings\shanselm\Desktop\xslt
> $b
xml                                     poo
---                                     ---
                                        poo

PS[6] C:\Documents and Settings\shanselm\Desktop\xslt
> $b.poo
c  : XYZ Inc.
a1 : One Abc Way
a2 : Some avenue
c1 : Tech city
c2 : Neverland

By the way, an unrelated-to-Powershell-but-related-to-XML note, do check out DonXML's XPathMania, a nicely integrated "why wasn't it built-in" Visual Studio add-in (screenshot).

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Review: Garmin Nuvi 350 GPS

July 21, '06 Comments [7] Posted in ASP.NET | Coding4Fun | DasBlog | Reviews | Subversion | Tools
Sponsored By

Scoble (BTW, thanks for the mention) asks Do GPS's work for you? Heck ya.

Nuvi350I love me some GPS. We did an article on hooking up to the Streets and Trips (Pharos) GPS with .NET based on EdJez and Kzu's original code.

I talked about GPS's and Geotagging in a podcast earlier this year after Patrick Cauldwell, a geocaching phenom, got me thinking about getting involved in geocaching as a hobby. Portland, where I am, is near where the very first geocache was placed - it's huge here in Oregon.

Once you're GPS-enabled, everything gets more interesting. Photos gain a new dimension if you GPS-tag them.

I researched the crap out of GPSs. The TomTom GO 910 is well thought of, particularly in Europe. It's got a 20gig hard drive with the USA, Canada and Europe installed. It's meant to be installed in your car(s) - not carried around. It's rather thick, bigger that I wanted. I also couldn't find it for less than about $790.

The Garmin Nuvi is similar to the Tom Tom. However, it's thin. It's about the size and thickness of a deck of cards, except about an extra .5" tall. it's quite thin, and the antenna (the foldy thing in the picture) folds flush. No attachments, no wires except power. It has an internal lithium battery. It pops right off the suction mount using the button at the bottom.

Like the Tom Tom it also plays MP3s, shows JPEGs, and hooks up directly to Audible. The Audible feature is nicely integrated - it will pause the book for a moment, speak the directions, then start up again having backed up a few seconds so you don't lose your place.

It also supports waypoints and POIs (Points of Interest) from 3rd party providers. Great for preparing a geocaching trip. (There's also lists of Red-Light Cameras you can load in) You tell it if you're in a car or walking (or in a cab, etc) and it gives directions based on that info. The Windows software is pretty sweet too. The Nuvi has an SD Card slot (same as my camera, yay!) and also shows up as a disk drive when plugged in using a standard USB cable. The software automatically updates the firmware and maps over the net when it's connected.

It also has a nice two-way language translator available and comes with sample data. You select/build sentences by filling in nouns and verbs on the touch screen for common questions: Where is the _____? and it translates and speaks the translation. Slick.

B000BKJZ9Q.01._AA280_SCLZZZZZZZ_Let me say the TTS (Text to Speech) voices are great. There's many to choose from. I use the Australian chick, she sounds the most natural. The Nuvi speaks the street names, you'll hear "Turn left on Murray Road..." and let me tell you, it makes a difference versus "TURN! Now!"

Here's some reviews of the Garmin Nuvi 350 GPS.

  • CNET User Reviews - "The Garmin Nuvi 350 is a Work of Art!"
  • RPV Blogster - "I would rank it at the top 1% of the most useful gadget I ever used.(sic)"
  • CNET Editors - "The high-performance Garmin Nüvi 350 should be at the top of your list."
  • Joe Mehaffy - "NuVi's GPS receiver sensitivity was the best we have reviewed."
  • 37Signals - "Insanely recommended."
  • PCMag - "The nüvi 350 from Garmin may well have you prying the plastic out of your wallet."

This thing retails for $900+ at Best Buy and other places, but you can get it for as low as $600 if you look closely.  It's $620 at Amazon right now. I expect the price on this to drop at least another $200 as Garmin starts to upsell the Garmin Nuvi 360 GPS. The only difference is that the 360 has Bluetooth Phone Support.

As much as I'd like Bluetooth, I really need to stop talking on the phone. I picked my Nuvi 350 up at the local Best Buy using a combination approach of a "12% of any single item" along with a price-match I forced on them from the Froogle. It CAN be found for around (or under?) $600.

The WAF (Wife Acceptance Factor) on this is huge. Mo isn't much for directions, counting mostly on monuments. There's been a few times where she got lost, drove home to get me, and had me drive her somewhere. Not being able to get somewhere is very helpless, and while Mo's not big on gadgets, she loves this GPS. The directions are clear, the touch screen is easy to use. Our favorite feature is the "Food near here." We recently had breakfast at the wonderful Sanborn's and never would have found it if we hadn't "asked."

Seriously, Robert, I've done the Streets and Trips "have the wife hold the laptop" thing. It really doesn't work unless you want to mount your TabletPC to your dashboard vents. Mappoint 2006 tried to get turn-by-turn directions right, but barely achieved a late-90s level of mediocrity. Bless them for trying, but Mappoint is a kick-ass geodatamining tool. However, it's directions are poo.

A dedicated, installed GPS is such a joy. They change one's life in a small, but measurable way like only two other devices have thus far - the iPod and the DVR.

Now, the real question, how will Scott Bellware afford a Nuvi?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

How to get Cookieless FormsAuthentication to work with self-issued FormsAuthenticationTickets and custom UserData

July 21, '06 Comments [2] Posted in ASP.NET
Sponsored By

Short answer: You can't.

I have an application that issues FormsAuthTickets like this...

// Create the authentication ticket                                   

FormsAuthenticationTicket authTicket = new

            FormsAuthenticationTicket(1,   //version

            userName,                    // user name

            DateTime.Now,                                //creation

            DateTime.Now.AddMinutes(Timeout),  //Expiration

            false,                      //Persistent

            MYPRIVATEANDVERYIMPORTANTDATA);

 

// Now encrypt the ticket.

string encryptedTicket = FormsAuthentication.Encrypt(authTicket);

// Create a cookie and add the encrypted ticket to the cookie as data.

HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName,encryptedTicket);           

...that was written in .NET 1.1. (FYI - It could have just as easily been written in .NET 2.0, there's nothing special here, but I want to write this code to support (be run under) both 1.1 and 2.0.)

The built-in ASP.NET helper functions SetAuthCookie and GetAuthCookie have been expanded to handle Cookieless formsauth in .NET 2.0.  So, if I used have used these methods in 1.1 I'd get the new funcitonality when my app was run under 2.0. However, I wanted to include UserData - extra encrypted context stuff - in my FormsAuthenticatonTicket, so the only choice was to issue the cookie myself.

For many of us, the promise of a cookieless Session AND cookieless FormsAuthentication is very exciting:

  <sessionState cookieless="true"/>
  <authentication mode="Forms">
   <forms name=".SOMEAUTH"
             loginUrl="default.aspx"
             protection="All"
             timeout="30"
             path="/Whatever"
             requireSSL="false"
             slidingExpiration="true"
             defaultUrl="default.aspx"
             cookieless="UseUri" />
  </authentication>

If you issue your own cookie like I do, adding it to Response.Cookies yourself as I do, your ASP.NET application won't get cookieless FormsAuthentication.

The problem is, System.Web.Security.FormsAuthentication.SetAuthCookie(String, Boolean) doesn’t allow the setting of UserData. (only String.Empty is passed in on creation of the ticket)

To be clear – if I could use SetAuthCookie and GetAuthCookie (the public static interfaces) I'd have been fine and received the new functionality. However, the UserData support is where this important scenario falls down. I thought I could roll this myself, but all the classes I need are very internal and more than a little icky.

A Microsoft ASP.NET insider said:

There isn't an API that allows the use of UserData with cookieless tickets.  Unfortunately UserData was [not included] in the cookieless forms auth implementation.

Conclusion: If you want cookieless FormsAuthentication you must use SetAuthCookie. Full stop. Until then I will find somewhere else to store my UserData.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

How to show multiple columns in an ASP.NET Mobile ObjectList

July 21, '06 Comments [1] Posted in ASP.NET
Sponsored By

An object list with one columnWhen using ASP.NET Mobile Controls to display a list of information, you'll usually use an ObjectList and databinding. ObjectList is a great control with lousy documentation.

When you turn off AutoGenerateFields and bind a collection of objects to the list - as I'm in banking, my example is always "Accounts" - you need to give a list of Fields that you want to bind to.

<mobile:ObjectList
        ID="listAccounts"
        Runat="server" 
        AutoGenerateFields="False" 
        TableFields="Description;AvailableBalance">
    <Command Name="AccountHistory" Text="Account History" />
    <Command Name="Transfer" Text="Make a Transfer" />
        <Field DataField="Description" Name="Description" Title="Account" />
        <Field DataField="NumberMasked" Name="NumberMasked" Title="Number" />
        <Field DataField="AvailableBalance" DataFormatString="${0:0,0.00}" Name="AvailableBalance"
            Title="Balance" />
        <Field DataField="Index" Name="Index" Visible="False" />

</mobile:ObjectList>

An object list with two columnsHere's the icky part. By default the list will only show the FIRST field - in my case "Description" - in the list. It'll be hyperlinked to a subform that will show the rest of the fields. Remember, we're designing for mobile here.

The useful little-known/documented trick is that if you put a semicolon separated list of DateFields in the TableFields attribute you'll get multiple columns when the ObjectList first displays.

One other unrelated note. For some reason when I use DataFormatString="C" to format the decimal AvailableBalance as a Currency, I get a string star-like character. Perhaps something wrong with the current UI culture. I worked/hacked around it with a format string like DataFormatString="${0:0,0.00}" for now.

 

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.