Friday, May 30, 2008

Aftermarket helmet lock

The saddle bags on our bikes cover the stock helmet lock rendering it completely useless, so we went for an aftermarket solution. Here's the site where we got it. I installed it today, and hung my helmet for the first time...pretty cool.

The Office & B.J. Novak

I'm so jazzed right now. I'm a huge fan of "The Office" and tonight I'm going to see B.J. Novak (Ryan) do stand-up. He writes for the show and produces too. This is going to be good!

Wednesday, May 28, 2008

Ordering columns in a GridView - it's not magic

I (and others) have found a bug in ASP.NET's new GridView control. Something that worked with the old DataGrid doesn't work in the new version.

Scenario: I had to reorder my gridview's columns at runtime to support custom report views for users. I have a list of columns that the person wishes to see, and a bunch of predefined columns built into the grid using the standard declarative syntax. So, in code, I loop through the user's requested columns and find them in the grid, then move them to the end of the grid by calling myGrid.Columns.Add(theColumn) and myGrid.Columns.RemoveAt(x) methods. I then set the visible property to false for all other columns. The columns reorganize just as I want, but any columns of type TemplateField end up with blank data (actually empty control collection) when the grid is rendered. Ouch! Apparently the template column data is saved in the viewstate, but not the template column's control tree, so when you move the column, that part is lost and the grid looks empty for those columns.

Unexpected solution: I'm using the gridview's built in sorting and paging....which is awesome, and surprisingly not dependent on EnableViewstate being set to true. Turning off EnableViewstate fixed my disappearing template column stuff and the sorting and paging still magically work.

Monday, May 19, 2008

Hangin' out & riding

Here's a pic of Rich, Kayleen, Stephanie, and me waiting to listen to Klementine play. That's a band that's playing at our wedding in October. Oh yeah, I guess I could've mentioned that earlier! Stephanie and I are engaged. Woohoo! Jes, the lead singer, took the photo. We all rode motorcycles to the bar. Don't worry, I drank a virgin rum & coke (aka a diet coke). Then we got home at midnight, woke up early the next morning and over Saturday and Sunday Steph and I rode 400 miles...yeah...wow. Saturday was blazing hot, about 90 degrees, which no matter how fast you're going is still hot. Sunday was cooler, under 80 degrees and going on the highway produced enough of a breeze for a really comfortable ride.

Thursday, May 15, 2008

Motorcycle MPG

It's silly when people think about getting a motorcycle to save money on gas. Sure, you'll get great gas mileage with a bike, but you just paid anywhere from $3k to $10k on the bike! So, I didn't spend money on a bike to save money on gas, but I am impressed with the mileage on these Hondas. My last 3 tanks I've gotten between 59 and 60 mpg. My car gets 38 to 40 and my old bike, a '96 Kawasaki ZX-11 Ninja, only got 37. I'm not saving a ton of money riding the bike over driving the car, but I do have a lot more fun.

Tuesday, May 13, 2008

SPSecurity.RunWithElevatedPrivileges

In the code-behind of a SharePoint page layout, I needed to modify the quicklaunch (left-side) navigation and add a few folders to the document library if they didn't exist. I retrieved the current SPWeb like this:
SPWeb web = SPContext.Current.Site.OpenWeb();
Then I made the changes I needed to to web.Navigation.Quicklaunch, etc. This all worked fine because of my permissions within SharePoint, but when our test user would go to the site, the page would fail with a message about "The list does not exist.". At the end of the AccessDenied.aspx URL was a GUID. I looked up the guid in the AllLists table in the database and found out which list it was. This was a red herring. There wasn't really a problem getting to the list because I was already accessing the list to populate some information on the page. Anyway, I tried to use SPSecurity.RunWithElevatedPrivileges(..) to make it so the regular user that was being impersonated could make the same navigation changes I was making as a site admin. It didn't work, but thanks to this forum entry I found out what I had to do. Basically, I had to spin up a new instance of the SPWeb and make my changes in that context. Here's what the code looks like:
SPSecurity.RunWithElevatedPrivileges
(delegate()
{
SPWeb aWeb = (new SPSite(web.Url).AllWebs[web.ID]);
aWeb.AllowUnsafeUpdates = true;

BuildLeftSideNav(aWeb);
});
So, the long story short...this is how you make RunWithElevatedPrivileges work. Also, you don't have to use an anonymous method like I did; you can pass a method name to RunWithElevatedPrivileges as long as it returns void and takes no parameters.

Tuesday, May 6, 2008

Using the ObjectDataSource

I've become quite fond of the ObjectDataSource control for binding to grids, etc. It's a beautiful thing, the ObjectDataSource control, as long as you're only dealing with one DataTable. If you hook it to a DataSet, it only grabs the first table (d'oh!), and makes a dataview of that. Now my queries may pull back one result set, but may also pull back more for certain reports. So, for a moment I figured I was back to setting the grid's .DataSource property and calling .DataBind()--yuck, thus losing the magical functionality of the ObjectDataSource control...but I found a way around it.

First off, my GridView and ObjectDataSource are now in their own .ascx control. In the report page, I call my query. I for-each through every table in the retured DataSet, dynamically loading the user control and setting a property on it.

DataSet ds = (new Report()).GetData();

foreach(DataTable dt in ds.Tables)
{
  controls_reportGrid rptGrid =
    (controls_reportGrid)LoadControl(
         "controls/reportGrid.ascx");
  rptGrid.Table = dt;

  phGrids.Controls.Add(rptGrid);
}

I created a Table property in the user control hoping to set the ObjectDataSource to a method that returns that table. Oops. The ObjectDataSource only looks in the App_Code folder (or bin folder) for objects. Now, to get around that, I've written the most obscure class that appears to do nothing useful, but it really does. It lives in the App_Code folder.

Here it is...

public class ReportData
{
  public ReportData(){}

  public DataTable GetData(DataTable dt){return dt;}
}

This lets me give my user control a DataTable, then the ObjectDataSource in the control calls ReportData.GetData(_table) in "TheDataSource_Selecting()" which just turns around and gives it right back...crazy, right?

I think the tehnical term is "jumping through hoops", but hey, it works.

Safari iFrame Gymnastics

I've got left side navigation that loads documents, either htm or PDF into an iFrame to the right. Every browser worked fine except Safari. I could load a web page into the iFrame, then another, but as soon as I loaded a PDF, it would never update when I set the 'src' attribute to a new URL. I popped an alert box to see what the src value was, and it _was_ setting it, but never rendered the new URL after I had put a PDF in there.

I opened the Javascript console, and saw a message having to do with cross-domain security, but I'm working in one domain, so I put that aside. I thought, well, I could tear down the iFrame, construct a new one on the fly and replace it in the DOM...it could work. I tried it and it worked, so I tried to simplify from there.

It turned out all I had to do was set the src to the new URL, then remove the iFrame from the DOM and just add it right back. That was enough to get Safari to render the darn thing.

Here's what I did...

var iFrame = document.getElementById('myFrame');
var parent = iFrame.parentNode;
parent.removeChild(iFrame);
parent.appendChild(iFrame);

Yeah...I know...this should really have no effect, but it works. The parent is just a div that only contains the iFrame, so I didn't have to worry about which child it was. It was the only child.

Remote Debugging

I've got an asp.net app running on a server, VS2008 running on my desktop, and I can attach the VS debugger to the w3wp.exe processes on the server. When you hit the website in the browser, the breakpoints in your local code just work. Here's how I set it up...
  1. I copied msvsmon.exe and msvsmon.exe.config to a folder on the server On my box, they were at "c:\program files\microsoft visual studio 9.0\common7\ide\remote debugger\x86" (There are x64 and ia64 folders as well)
  2. I launched msvsmon.exe on the server This creates a monitor called [myLoginName]@servername
  3. In the Tools-Permissions window, I added my regular login account as having debug rights (I have to log into the server as an admin account)
  4. On my dev box, in Visual Studio, I chose Debug-Attach to process
  5. I typed in [myAdminLoginName]@servername to match the name of the monitor
  6. I clicked the Refresh button, and voila! -- a list of processes
There's one downside. When I do step 3, then close msvsmon.exe, it doesn't remember that I added my account the permissions, so each time I run it I have to re-add the permission. Luckily remote debugging isn't something we do on a daily basis.

Matching Process ID with App Pool

I was troubleshooting a memory problem with a group of ASP.NET sites in a particular App Pool. In IIS, I can see which applications are in which application pool, and in the Task Manager, I can see multiple w3wp.exe processes, but how do you tell which Process ID goes with which App Pool? There's a simple command line you can run on Windows Server 2003 to do just that. cscript.exe %windir%\system32\iisapp.vbs This gives you a nice dump of exactly what you need. W3WP.exe PID: 1400 AppPoolId: MyAppPool1 W3WP.exe PID: 920 AppPoolId: SomeOtherAppPool

IE Dom and Custom HTML Tags

I've been doing some HTML DOM parsing do deal with JSON return values and decided it would be quicker to use document.getElementsByTagName('myCustomTag') to get just the pieces I wanted.

This works fine, except in Internet Explorer. It finds only the opening tag, and says that it has no children. Of course, the other browsers did it right and it works fine.

So, the trick you have to do with IE to make this work has to do with the HTML namespaces.

In your <html> tag, include xmlns:blah=http://www.blah.com (obviously customizable)

Then name your custom tags like this ... instead of just ....

Now, when you do a document.getElementsByTagName('MyTag')[0] you'll get a full-fledged DOM object with children, etc. It's not the most obvious thing to do, but it works.

Encrypting the identity tag in web.config

In the web.config file, under you have

Here's a sample command line that encrypts the identity tag. This is all one line by the way...

%windir%\microsoft.net\framework\v2.0.50727 \aspnet_regiis -pef system.web/identity d:\locationToMyWebsite\webSiteName -prov DataProtectionConfigurationProvider

To undo it, just replace "pef" with "pdf" and remove the -prov switch. The "e" is for encrypt, and the "d" is for decrypt. Also replace the v2.0.50727 to the version of .Net you're using if it's not 2.0.

You could go through the trouble of creating your own provider, exporting keys to XML files for use on a server farm, etc. but if you're just running an app on one server, take the easy road and just use the DataProtectionConfigurationProvider. Keep in mind that you need to deploy first, then do the encryption because if you do it on one machine, then move it to another, it won't work.

Monday, May 5, 2008

Camping

This last weekend was my first time (ever) camping, and it was really fun. Part of it was because it was new, but most of it was because my lady is an experienced camper. We had everything we needed. Not just stuff, but processes about how to deal with the stuff. The trip was very organized; the kind of organization you'd expect if you paid some outdoors firm to take you camping if you had no idea what to do or what to bring. There was no rushing, no stress, and no computers. (c'mon, I need to get away from 'em sometimes) The weather said chance of rain, but it didn't even sprinkle until we had everything set up Friday, then a little bit over night and Saturday morning. We were perfectly dry the entire time. Here's what the campsite looked like when we got there, and then after we'd set up. The trees were neat. A lot of them were curvy and bumpy with moss and other plants growing on them. Here's one that looks like it has a tumor. It was like visiting an old neighborhood where every house looks different. This wasn't a bunch of straight power-pole-like trees. Although if you look to the left of the tumor you can see a power pole. It's only a short walk to the beach, and by the looks of this sign, if you have a really-really-really old car, you can't go down this particular path. This heron was eating well. He'd walk ever so slowly then dart his head in the water and swallow what he caught. He was fun to watch. This piece of wood sticking out of the ground looks like a little city in some Lord of the Rings type of movie. I can imagine little people walking around everywhere. The coolest thing at the beach was the millions of black sand dollars. Maybe there's another name for them, but that's what they looked like to me. Here's a close-up. And here's a broader shot...literally, millions of them.

Friday, May 2, 2008

SharePoint bug in GetFormattedValue

I'm grabbing list values programmatically in SharePoint. On the Microsoft.SharePoint.SPListItem, I call GetFormattedValue("myFieldName") and if that field is a date, it displays the day before the date that's stored in the list. Ouch. To get around it, I used the following: ((DateTime)itm["Est. Start Date"]).ToShortDateString()

Mmmm... leather...

Here are some more pictures, showing off our leather. :) We both have Thinsulate lined gloves for cold weather, and the other morning we found out that they work, but not for long. 30 minutes into our ride our hands were freezing cold. But it was 38 degrees when we left, so maybe that's to be expected. ha ha. Anyway, we'll be looking into getting heated hand grips for the cold mornings, evenings, etc.