Displaying HTML Strings in the Windows Phone 7 WebBrowser

Turns out I shouldn’t code after midnight. As Bill points out below, the WebBrowser control’s slightly poorly named NavigateToString method will do all of this in one call. God do I feel stupid right now.

I have however sorted out getting links to open a proper BrowserTask, and have also got my css formatting changing to match the phone’s theme. I’ll write that up later once I make sure it’s not simplified elsewhere.

I’m building a Windows Phone 7 application, and I have a need to show nicely formatted HTML from a dynamic source, but not a web server. I hacked around with Silverlight’s TextBlock with its Inlines property for a while, but the lack of anything other than a plain Run object leaves me short. I can’t easily render links and lists.

My hint came from this MSDN article, describing how to display resource files by first writing them to Isolated Storage. My solution assumes you have an object (hopefully a ViewModel) with one or more properties that hold raw HTML. Mine is hardcoded below, but let’s assume you’re getting it from some sort of data store or API.
[csharp]
public string Description
{
get
{
return
@"<body style=""font-family:’Segoe WP’;font-size:46px"">
<p>Sample Readme Content</p>
<p>Here’s another paragraph
<a href=""http://ben.geek.nz"">with a link</a> and
<b>some bold, <u>underlined</u> text</b></p>
<ul><li>And a list</li><li>With two items</li></ul>
</body>";
}
set { }
}
[/csharp]

You might already see where I’m going with this. We co-opt the SaveToIsoStore method from Microsoft’s example, and use it for our own nefarious purposes. I write our HTML out to a file, then return a Uri pointing to that file:
[csharp]
private const string CurrentDescriptionFileName = "CurrDesc.html";

public Uri DescriptionUri
{
get
{
IsoStore.SaveToIsoStore(
CurrentDescriptionFileName,
new System.Text.UTF8Encoding().GetBytes(Description));

return new Uri(CurrentDescriptionFileName, UriKind.Relative);
}
}
[/csharp]

Then we can set our WebBrowser control to point to this Uri. Unfortunately in the current build there is no bindable Uri property on the control, so we have to resort to ugly old code-behind to tell the WebBrowser to load our page. The code below assumes you’ve already set a DataContext, so we just grab a reference to it and call the DescriptionUri we created above:
[csharp]
private void webBrowser1_Loaded(object sender, RoutedEventArgs e)
{
var dc = (MyViewModel) this.DataContext;
webBrowser1.Navigate(dc.DescriptionUri);
}
[/csharp]

WP7 Html HackEt voila! Our dynamic HTML string appears all lovely and formatted in the browser control. The main issue I have at the moment is that I can’t seem to force links to open in the external browser. I guess I’ll have to catch the click event in script, and send it out to the host control.

So there you have it. I never said it was pretty, and it does seem a bit heavyweight to go hammering on isolated storage, but it does work!

Tech.Ed and Auckland .NET Code Camp 2009

You may or may not know that my day job involves bitwrangling for Datacom, using Microsoft development platforms (yes yes evil empire yadda yadda – give me a break). As such, I’m excited that Tech.Ed New Zealand is a mere two weeks away. Tech.Ed is the 3-day conference where Microsoft developers (developers! developers!) and systems people get the low-down on the latest development tools and techniques.

Yes I know, among the gems lie some turds (“Look! Here’s how you can convince customers to spend MORE money on MORE Microsoft licenses (that they may or may not need)!”), but quite frankly that comes with the territory. You learn to pick the good sessions. Besides, every good developer knows the warm fuzzy feeling from a well-implemented system completely obliterates any debate about license cost vs FOSS. Just look at TradeMe and Xero. Do you care that they aren’t LAMP sites?

TCode Camp Logohere’s more to come on the Tech.Ed front – myself and Mauricio will be blogging and vlogging from the show floor.

And then there’s Code Camp. Even if you aren’t attending Tech.Ed, if you’re a developer you should get yourself to Code Camp on the Sunday. It’s free for everyone, not just Tech.Ed attendees. You can sign up here, and session details are here.

I’d be at code camp with bells on if it weren’t for a certain wee 2-week old girl.

Disclaimer: Datacom are gold sponsors for Code Camp.

Silverlight: Flash doesn’t matter

Microsoft SilverlightAfter listening to The Gang XVII, with its usual nuggets of awesomness (awesomity?) buried beneath hours of non-sequitur and Jason “Idiot Savant” Calacanis, I had to comment on something. The topic of Silverlight came up, and the comment was (paraphrasing) “why would all these Flash and Air developers bother to switch to Silverlight?”

Here’s the answer that wasn’t touched on in the show: it doesn’t matter a goddamn. Microsoft doesn’t need anyone to switch anywhere. They already have the largest and most influential group of developers on the planet a massive number of .NET developers. And you can bet that (just like .NET) there will be a Silverlight interpreter for Java.

Think back to VB6 and even VBA. It makes no difference what the academics think about a language or platform. If you build it, and it’s easy to use, they will come. It will take just one or two good viral apps to get Silverlight ‘stealth installed’ on the majority of PCs. To date the only Air app I’ve seen was the Pownce client, and it just pissed me off the way Air was so intrusive and non-intuitive. Adobe: I don’t want to run web apps on my desktop!

I want to run desktop apps on my web.

Fun StackTrace of the Day

I love a good ambiguous error message as much as the next guy, and injecting some fun into those error messages always makes my day. We came across a strange error in our database connection code the other day, and the following (100% genuine, Microsoft generated) stacktrace was part of the error:

[code]Stack Trace
at System.Data.SqlClient.SqlTransaction.Zombie()
at System.Data.SqlClient.SqlInternalTransaction.ZombieParent()
at System.Data.SqlClient.SqlInternalTransaction.CloseFromConnection()
at System.Data.SqlClient.TdsParser.Deactivate(Boolean connectionIsDoomed)
at System.Data.SqlClient.SqlInternalConnectionTds.InternalDeactivate()
at System.Data.SqlClient.SqlInternalConnection.Deactivate()

Seems my connection became doomed, then went on a search for braaaaains!!

Response.Redirect Draws a Blank

Found a nasty little bug in ASP.NET 1.1 this morning. Once again I’m documenting it for my personal archives and the Googlers.

This one results from using a combination of Response.Redirect, SmartNavigation and secure (HTTPS) connections. The page in question was designed to save changes, then redirect to the original calling page, so the contents of the event called from the Save button did something like:

SaveData();
Response.Redirect( myCallingPageURL );

This worked perfectly on the dev box, but when migrated to the test machine, it resulted in a blank page, with the address still set to the original page. Debug output proved that the Response.Redirect call was being made correctly. The only difference was that the test machine was configured to use SSL (HTTPS), and the dev machine was not.

After a bit of Googling, I found the answer right here. The culprit was the SmartNavigation property on the page. SmartNavigation, when set to true, is a nifty little add-on that makes page updates very smooth in Internet Explorer. You don’t get a visible page refresh, and some Javascript jiggery-pokery is used to make post-backs almost transparent. Very nice when you’re designing for an IE-only corporate client (shudder).

BUT, SmartNavigation seems to bugger up Response.Redirect when using a HTTPS connection, as we witnessed. Thankfully the solution is pretty simple, and does not require disabling SmartNavigation for the whole page life. You just need to do it prior to calling the Response.Redirect like so:

SaveData();
this.SmartNavigation = false;
Response.Redirect( myCallingPageURL );

That simple addition will turn off the javascript stuff for the single callback that results in the redirect. Problem solved!