Wednesday, 28 July 2010

Visual Studio 2010 Remote Debugger Location

You'll find the VS 2010 remote debugger a similar location to that of the 2005/2008 debuggers:

C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\Remote Debugger

Note VS2010 is still a 32-bit app so if you're running a 64-bit OS, you'll need to be looking in the Program Files (x86) directory. The debugger itself comes in both x86 and x64 flavours.

To install, I normally just copy the relevant architecture folder to the remote server. Run msvsmon.exe and assuming you're otherwise setup for remote debugging, it should be same as previous versions of Visual Studio!

Ps. You can also download the remote debugger from Microsoft.
Pps. I normally add msvsmon to the Windows Startup folder so it starts whenever I boot my remote dev environment (I've found I previously had to log on to the server, however, and two users can't run the monitor simultaneously on the same server). Apparently the 2010 monitor can be run as a service—not sure if this was the case for previous versions.

Friday, 23 July 2010

PublishingPageLayout doesn't point to current web application

In the past, we've developed a habit of "birthing" new SharePoint sites in a UAT environment for initial setup and stabilisation before the big move to the production farm; during major site renovations—like the recent www.westernaustralia.com rebrand—we'll implement a quasi-freeze on production (emergency edits only) and conversely pull the content down to UAT for overhaul. At the end of the day, we're moving SharePoint content between farms on a fairly regular basis.

Prior to the release of the April 2009 Cumulative Update packages, Microsoft didn't support moving content databases between farms because "MOSS often stores absolute URLs to the Page Layout in the properties of a Publishing Page." With the April Cu that changes but of course the times they a change too and we've abandoned the stsadm backup/restore commands in favour of restoring SQL Server backups and running the stsadm deletecontentdb/addcontentdb commands (it's not only more reliable but many times faster…).

Until yesterday none of the above was causing us any problems. In fact, we'd even become complacent about it all.

So finding out one of our content editors was unable to publish a page due to a previously unseen exception was not something that would have led us to review the way we handle content databases. After checking out some pages for editing, an attempt to publish the page was met with the following exception:

ArgumentException 
Value does not fall within the expected range.

    Stack trace:    at Microsoft.SharePoint.Library.SPRequestInternalClass.GetMetadataForUrl(String bstrUrl, Int32 METADATAFLAGS, Guid& pgListId, Int32& plItemId, Int32& plType, Object& pvarFileOrFolder)
   at Microsoft.SharePoint.Library.SPRequest.GetMetadataForUrl(String bstrUrl, Int32 METADATAFLAGS, Guid& pgListId, Int32& plItemId, Int32& plType, Object& pvarFileOrFolder)
   at Microsoft.SharePoint.SPWeb.GetMetadataForUrl(String relUrl, Int32 mondoProcHint, Guid& listId, Int32& itemId, Int32& typeOfObject, Object& fileOrFolder)
   at Microsoft.SharePoint.SPWeb.GetFileOrFolderObject(String strUrl)
   at Microsoft.SharePoint.Publishing.CommonUtilities.GetFileFromUrl(String url, SPWeb web)
   at Microsoft.SharePoint.Publishing.PublishingPage.get_Layout()
   at Microsoft.SharePoint.Publishing.PublishingPage.GetEffectivePageCacheProfileId(Boolean anonUserProfile)
   at Microsoft.SharePoint.Publishing.PublishingPage.GetEffectiveAnonymousPageCacheProfileId()
   at Microsoft.SharePoint.Publishing.CachedPage..ctor(PublishingPage page, SPListItem altItem, String id, String parentId, String title, String url, String description, CachedObjectFactory factory, List`1& fieldInfo, Boolean datesInUtc)
   at Microsoft.SharePoint.Publishing.CachedPage.CreateCachedPage(PublishingPage page, SPListItem altItem, CachedObjectFactory factory, List`1& fieldInfo, Boolean datesInUtc)
   at Microsoft.SharePoint.Publishing.CachedPage.CreateCachedPage(PublishingPage page, CachedObjectFactory factory, List`1& fieldInfo, Boolean datesInUtc)
   at Microsoft.SharePoint.Publishing.CachedListItem.CreateCachedListItem(SPListItem item, SPListItem alternateItem, Boolean parentIsWeb, CachedObjectFactory factory, List`1& fieldInfo, Boolean datesInUtc)
   at Microsoft.SharePoint.Publishing.CachedObjectFactory.CreateObject(SPListItem listItem, List`1& fieldInfo, Boolean datesInUtc)
   at Microsoft.SharePoint.Publishing.CachedObjectFactory.CreateObject(PublishingPage page, Boolean datesInUtc)
   at Microsoft.SharePoint.Publishing.CachedObjectFactory.GetPageForCurrentItem()
   at Microsoft.SharePoint.Publishing.TemplateRedirectionPage.ComputeRedirectionVirtualPath(TemplateRedirectionPage basePage)
   at Microsoft.SharePoint.Publishing.Internal.CmsVirtualPathProvider.CombineVirtualPaths(String basePath, String relativePath)
   at System.Web.Hosting.VirtualPathProvider.CombineVirtualPaths(VirtualPath basePath, VirtualPath relativePath)
   at System.Web.UI.DependencyParser.AddDependency(VirtualPath virtualPath)
   at System.Web.UI.DependencyParser.ProcessDirective(String directiveName, IDictionary directive)
   at System.Web.UI.PageDependencyParser.ProcessDirective(String directiveName, IDictionary directive)
   at System.Web.UI.DependencyParser.ParseString(String text)
   at System.Web.UI.DependencyParser.ParseFile(String physicalPath, VirtualPath virtualPath)
   at System.Web.UI.DependencyParser.GetVirtualPathDependencies()
   at Microsoft.SharePoint.ApplicationRuntime.SPVirtualFile.CalculateFileDependencies(HttpContext context, SPRequestModuleData basicRequestData, ICollection& directDependencies, ICollection& childDependencies)
   at Microsoft.SharePoint.ApplicationRuntime.SPDatabaseFile.EnsureDependencies(HttpContext context, SPRequestModuleData requestData)
   at Microsoft.SharePoint.ApplicationRuntime.SPDatabaseFile.EnsureCacheKeyAndViewStateHash(HttpContext context, SPRequestModuleData requestData)
   at Microsoft.SharePoint.ApplicationRuntime.SPDatabaseFile.GetVirtualPathProviderCacheKey(HttpContext context, SPRequestModuleData requestData)
   at Microsoft.SharePoint.ApplicationRuntime.SPVirtualFile.GetVirtualPathProviderCacheKey(String virtualPath)
   at Microsoft.SharePoint.ApplicationRuntime.SPVirtualPathProvider.GetCacheKey(String virtualPath)
   at Microsoft.SharePoint.Publishing.Internal.CmsVirtualPathProvider.GetCacheKey(String virtualPath)
   at System.Web.Compilation.BuildManager.GetCacheKeyFromVirtualPath(VirtualPath virtualPath, Boolean& keyFromVPP)
   at System.Web.Compilation.BuildManager.GetVPathBuildResultInternal(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile)
   at System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile)
   at System.Web.Compilation.BuildManager.GetVirtualPathObjectFactory(VirtualPath virtualPath, HttpContext context, Boolean allowCrossApp, Boolean noAssert)
   at System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(VirtualPath virtualPath, Type requiredBaseType, HttpContext context, Boolean allowCrossApp, Boolean noAssert)
   at System.Web.UI.PageHandlerFactory.GetHandlerHelper(HttpContext context, String requestType, VirtualPath virtualPath, String physicalPath)
   at System.Web.HttpApplication.MapHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, Boolean useAppConfig)
   at System.Web.HttpApplication.MapHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Which of course means nothing.

Before long the guys stumbled across one of Stefan Go├čner's brilliant posts, which suggests MOSS doesn't update all absolute page layout URLs on each publishing page object when content is shifted between farms; of cours, with the April 2009 CU installed and doing things via backup/restore or import/export, we may have avoided this issue.

You can see the exact problem by examining a page in SharePoint Manager: locate a page in the Pages library, expand the page node to reveal its Properties node, and expand the Properties node so you can inspect the PublishingPageLayout node. This screenshot is from my development environment, a server named dev-moss-mh5 and the wa.com web application configured to run on port 180—so obviously the http://edit.uat.westernaustralia.com value is incorrect:

SPM 2007 Publishin Page Properties

Interestingly, this problem was only affecting the production edit site… for whatever reason, my dev environment was unaffected. However, running Stefan's FixPageLayout code corrected the problem in production. The application runs through every page in every subsite and fixes the PublishingPageLayout property where required (in addition to reporting any other page instance/layout/master page issues). 

Update: Just remembered from a while back Gary Lapointe also has an stsadm command to fix this problem:

http://stsadm.blogspot.com/2007/08/fix-publishing-pages-page-layout-url.html

Thursday, 22 July 2010

Subsites, the Pages lists and 302 Redirects in an anonymous environment

Our network admin recently noticed some unusual errors in the proxy logs: www.rottnestisland.com/en/pages was failing to redirect to the default page. Fiddlering the request revealed a 401 response was being returned by SharePoint and friends.

Naturally our first inclination was to scratch our heads and wonder why such a thing would occur; at first glance both /en and /en/pages share many similarities with regular IIS directories; shouldn't SharePoint just go looking for the default page?!? After all, a request for /en is successfully 302 Redirected to /en/pages/home.aspx and the authenticated site also redirects the /en/pages request.

In the above case, home.aspx is the default page as configured in the Pages list and it is published along with its master page and other supporting resources (in fact the page loads without issue when requested directly).

Our anonymous setup isn't out of the ordinary: a read/write web app running in the default security zone is extended to a second read-only web app running in the internet zone with anonymous access enabled. Most annoyingly, we've got some URL rewriting tricks going on with www.westernaustralia.com so that site (which runs in the same farm as Rottnest) does redirect the /pages directory, as does its sister web site, www.tourism.wa.gov.au… the latter of which should have no tricks applied!

Until this point we assumed those of us in the naughty developer team had broken something on the Rottnest site and those of its sibling sites running the same code base. When I spun up a sanity web app in my dev machine, however, I found I could reproduce the problem in that environment with no customisations applied.

From one point of view this all makes sense: subsites will 302 Redirect to the default page in the Pages library and behave in the "normal IIS way"; the Pages library, being a list, should not do the same and does not—it's not a site. While plausible, this argument falls down when everything just works in the authenticated site.

No deeper understanding or solution to this one as yet…

SharePoint 403 Forbidden and /bin directory read access

We've run a post-deployment script in our SharePoint environment for many years now to address a 403 Forbidden error that otherwise plagues us; the script grants the Authenticated Users account read access on the web application's bin directory and web.config file (for both the authenticated and anonymous sites).

Someone deemed this necessary back in the day and, until recently, I always assumed it was just some quirk with our crusty environment. That was until I came across the same problem at another client site where custom code (in a SharePoint solution) was being deployed.

With that as motivation, I decided to figure out why we do what we do and determined a .NET 2.0 security update from July 2007 introduced the problem:

The performance of the server may decrease and adding users or uploading files may fail with the error message "403 Forbidden error" after you install a Microsoft .NET Framework 2.0 hotfix.

The security update links to a SharePoint 2007 hotfix:

In SharePoint Server 2007, the performance of the server may decrease, and you cannot upload files or add users. Additionally, you may receive the following error message:

403 Forbidden error

This issue occurs after you install a Microsoft .NET Framework version 2.0 hotfix that is build 2.0.50727.832 or a later build.

In the www.westernaustralia.com context, the problem normally crops up when an anonymous user requests a page that runs custom code; with my second client, the problem occurred in the authenticated site when a content editor with full permission over the site collection attempted to access a SharePoint list. The 403 response seemed to be intermittent but I suspect it was application recycles correcting the problem for a short while. As a user with local admin rights on the WFE, I wasn't seeing the problem myself.

Apparently uninstalling the update may be an option but I wonder if it's been incorporated into .NET 3.5 so that may not be possible in this day and age.

Anyway, the consensus around this problem is that it's caused when a request from an authenticated user without local admin rights results in a failed read of the /bin directory by the impersonating w3wp.exe process (as evidenced by filemon.exe). The solution is to ensure the Authenticated Users group has read access to the /bin directory below C:\inetpub\wwwroot\wss\VirtualDirectories\{your app}

Notably, we've found this permission needs to be reapplied as part of every deployment and often find permissions have reset after touching the Authentication providers settings in Central Admin.

Thursday, 15 July 2010

Dumb User

A web site user rang the support desk to complain a thumbnail image of theirs wasn't displaying on our web site. We asked them to send a screenshot of the problem; this is what we got back (names altered to protect the bright sparks involved):

From: Best Resort [mailto:visit@bestresort.com]
Sent: Thursday, 15 July 2010 12:40 PM
To: 'Carol Belding'
Subject: Best Resort Broken Image

Hi Carol

Printed and scanned page for you. 

Regards
Fran
Best Resort

Scan0715 I've been in IT too long…

Tuesday, 13 July 2010

CSS background-position not applied

Had a weird issue with a CSS sprite this morning where both IE8 and Firefox ignoring the background-position style. After stripping the component back to bare bones, the style started working as expected; I was momentarily stumped until a co-worker spotted the fact I didn't have any units specified for the x and y coordinates. Adding px to each coordinate resolved the issue.

.addLeg
{
    background-image: url(Buttons.png);
    background-position: -10px -70px;
    width: 143px;
    height: 30px;
}

Doh! That's what I get for copying code from the internet! Maybe something to do with the nesting or inherited styles? Who knows…

In related news, I also noticed the IE Dev Toolbar in IE8 is inclined to display additional styles seemingly appended to the background-image style. This is a minor annoyance in terms of being able to manipulate the appended style but it seems to be a bug with the toolbar, not IE as the styles are applied correctly.



IE Dev Toolbar Background Image Style










Sunday, 11 July 2010

C# Object Initialisers and trailing commas

C# 3.0 (.NET 3.5) introduced some really handy new features and hidden gems—and I'm still coming across the latter today.

If you've been developing with C# 3.0 for a while, you'll know about object and collection initialisers: they allow you to initialise the members of an object or a collection at instantiation-time, rather than having to declare a variable and then assign values to the members of that variable. It's a convenience thing and makes life easier in a LINQ context (although the debugging experience in VS2008 sucks).

When looking back through a piece of code I written previously, I noticed I'd inadvertently left a trailing comma after the final assignment in the list. I thought this was unusual because the code compiled and the C# is normally anything but lazy when it comes to most language constructs.

StaffMember staffMember = new StaffMember()
{
    FirstName = "Michael",
    Surname = "Hanes",
};

Sure enough, the ECMA C# Language Specification actually notes the following in the section about Array Initializers: "[…] C# allows a trailing comma at the end of an array-initializer. This syntax provides flexibility in adding or deleting members from such a list […]" (page 363, section 19.7). Interestingly, object and collection initialisers simply inherit this behaviour and the ability to initialise both arrays and enums in this way is not C# 3.0-specific (the specification document actually cites the C language).

In my opinion this is a rational design decision that increases the useability of the C# language by developers. By contrast, I'm forever having to shift SQL select statement commas around when commenting out individual lines and it's a major inconvenience. Development is normally a very fluid, experimental time in the lifecycle of a code artefact and anything that simplifies that exercise and increases maintainability is a good thing.

Thursday, 8 July 2010

How to recover permanently deleted files in Outlook

I receive hundreds of emails a day from various systems and the occasional email from humans; in both cases, most of that email gets deleted straight away. Because my Deleted Items folder clogs up quickly as a result of this barrage, I've become a chronic shift-delete kind of guy over time—in other words, get rid of it, I don't want to ever see it again, goodbye. This generally works quite well for me and I've never had the need to recover something I've permanently deleted. Until today.

Despite it's fancy new ribbon, Outlook 2010 still allows me to delete all email below the Today group (and other groups)—a complete useability flaw, if you ask me but I'm not a Microsoft genius and I suppose the more barbaric users out there may actually use this "feature".

So I accidentally did just that today—my machine was bogged down running a SQL query: my fat fingers accidently moved the highlight to the Date: Today heading at about 2:30pm, I hit the shift-delete combo, said No I don't want to permanently delete when prompted (having realised what I'd done), and everything that I had yet to file from my Inbox and actually wanted to keep got deleted anyway.

Sigh. The IT guys proposed some registry hacks that didn't seem to apply to 2010 until someone finally found the almighty Recover Deleted Items button on the Folder menu:

Outlook-recover-deleted-itemsClicking this button brings up a list of email messages it can recover from somewhere (Exchange? I don't know and don't care) with details about date, sender, subject, etc. You can recover all files or individual files. 

Monday, 5 July 2010

jquery.jsonp addresses issues with beforeSend and timeout

On the coat-tails of my previous post, I've come across the Google Code jquery-jsonp library and it's a saviour if you're working JSONP requests. Remember a JSONP request dynamically inserts a script tag into the DOM and doesn't use the same XMLHttpRequest as your typical AJAX request.

I ended up here because—much like beforeSend—the timeout parameter wasn't having any effect on my jsonp request: the request simply wouldn't time out despite the value supplied to this parameter.

In addition to supporting both beforeSend and timeout properties, jquery-jsonp adds some really nice caching support—both typical HTTP (aka "browser") caching and page-based caching that leverages the JavaScript VM's memory (apparently). Anyway it seems to work as advertised. Perhaps most importantly, the name of the callback can be changed.

A minified version of the code is available from Google Code and the documentation is concise and well-written with examples. 

Friday, 2 July 2010

.ajax beforeSend Doesn't Fire

I absolutely adore jQuery but there are some things that are just plain hard to do with the current version. One of those is cross-domain (JSONP) requests. No only is the setup convoluted but the requirements are exacting to arrive at a working solution for something that should be (?) reasonably simple. But I'll save the details for another time—for now, just know I'm building with jQuery and using JSONP to issue cross-domain requests.

Under the covers, that cross domain request is actually being issued by dynamically loading a new script tag in the DOM, even though everything's still handled through the $.ajax() function. It's workable, of course, but since there are no XMLHTTPRequests involved, the behaviour is apparently somewhat different. In my particular case, I was wiring up  'beforeSend and 'complete' event handlers to show and hide a spinner across the lifetime of the request; the 'complete' handler was firing fine but the 'beforeSend' handler wasn't firing at all.

A response by SLaks to a Stack Overflow post reminded me the request mechanism with JSONP is different and implied 'beforeSend' wouldn't work with this setup. His response instead suggested showing the spinner after the $.ajax call and this did the trick for me.