Showing posts with label SharePoint. Show all posts
Showing posts with label SharePoint. Show all posts

Thursday, 25 October 2012

SharePoint 2013 RTM on MSDN

The SharePoint 2013 RTM bits and the Office 2013 bits are now available on MSDN.

Tuesday, 15 February 2011

How to reference a user control deployed to the GAC

Warning: I don't consider myself a control developer in the classic sense (although I frequently write web parts for deployment within a SharePoint environment, the scope of that deployment is fairly small by default).

A lot of the functionality I build these days runs client-side, powered by my all-time best friend jQuery. Because of this, I find I'm most productive when I first establish the HTML structure in a static HTML file before moving the mark-up to a control. In the bad old days of complete ignorance, I would have had refactor this HTML and build it programmatically within a custom control—an approach I despise (HTML belongs in mark-up).

To overcome many of the drawbacks to custom control development, I prefer migrating my prototype to a user control (which has a front-end .ascx file) and then loading that control in my web part code-behind or custom field control. Check out the LoadControl documentation for information about how load a user control programmatically.

This works beautifully when the control's code behind is compiled to an assembly destined for deployment to the private bin directory. The user control can be built in isolation with the ascx file and code behind remaining wired up for easy access to Intellisense, etc. At build time, the .ascx file is copied somewhere and deployed somewhere useful (the CONTROLTEMPLATES virtual directory, if you like).

Things get trickier when that assembly is to be deployed to the global assembly cache (aka the GAC). In my case I wanted to do this for a custom field control; although most of the code for that control was already being deployed to the GAC, it made sense to keep these artefacts together in the same project. For my non-SharePoint readers, any assembly going into the GAC has to be strong named and signed (via the project's properties sheet > Signing tab); to add the assembly to the GAC, call gacutil:

gacutil –i "MyAsemblyName.dll"

With that out of the way, assume we've got two projects: the first (MyControls) is a class library outputting a signed assembly intended for the GAC; the second is a simple web site (Web).

GAC_User_Control_sln

The MyControls project contains our user control (for information about how to set this up, refer to my post How to add a web project item to a class library). The MyControls assembly is deployed to the GAC.

The web site project contains a copy of the .ascx user control file from the MyControls project and a web page with a @ Register directive pointing to the project-local .ascx file. The Web project doesn't reference the MyControls project because we want it to load the assembly it depends on from the GAC. The .ascx copy can be done manually but you'll likely want to automate this as a pre-build task.

While the MyControls project will now compile, the Web site project will fail to compile with the error: Could not load type 'MyControls.MyUserControl'. If you're in a SharePoint environment, you'll likely see get this as a parser error when the page is dynamically compiled at first request.

To fix this, you need to add an @ Assembly directive to the top of the .ascx file to reference the MyControls assembly deployed to the GAC. You'll need the assembly name and public key token to flesh out this directive. The assembly name can be retrieved from the project properties sheet (normally it's the same as the project anyway). Then extract the public key token using the strong name application (it's the short value):

sn –Tp "MyControls.dll"

If your AssemblyInfo.cs specifies a version number of 1.0.0.0, your @ Assembly directive should look something like this:

<%@ Assembly Name="MyControls, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=b3de351d91c5d4d2"%>

If you version numbers are automatically updated by some kind of policy or build event, beware you'll also need to update this directive as well, which may prove cumbersome.

This directive can be added to both copies of the .ascx file without impacting the MyControls build or edit-time experience.

Both projects will now compile and run, successfully loading the user control base type (i.e. the code behind) from the GAC.

You can download this solution here.

If you found this post helpful, please support my advertisers.

Wednesday, 15 December 2010

How to disable web part chrome

In the context of a highly-branded WCM site, the default "chrome" (border and title) SharePoint wraps around your custom web parts can be ugly and inconvenient. At worst, it will make your site look SharePoint-y and require every web part added to a page to have its Chrome settings manually adjusted.

Chrome Type

That's no way for a web part to behave in content management system but what to do?

In the past I've set the chrome type explicitly for each custom web part, either in the .webpart/.dwp file itself or programmatically:

<property name="ChromeType" type="chrometype">None</property>

this.ChromeType = PartChromeType.None;

To avoid doing this per web part (or cluttering up your nice little base web part class), you can set the PartChromeType property on the WebPartZone declared in your page layout:

<WebPartPages:WebPartZone id="wpzLeftColumn" runat="server" title="Left Column" PartChromeType="None">

Any web parts added to that zone with a default Chrome Type (aptly named "Default") will inherit this setting from the web part zone. Of course individual web parts can override this as required.

If you found this post helpful, please support my advertisers.

Enumeration-based property with initialiser fails to load in tool pane

I encountered what is likely an obscure issue today while using an existing enumeration to populate a drop down list in the tool pane of a SharePoint web part.

The web part didn't require a custom toolpart so I'm referring to a web part property defined within the web part class itself:

[SPWebCategoryName ("TWA")]
[FriendlyNameAttribute ("Region Override")]
[WebDescription ("Explicitly configure the region displayed when the mode is set to Region.")]
[Personalizable (PersonalizationScope.Shared, false)]
[WebBrowsable (true)]
public WARegions RegionOverride { get; set; }

Notice how the public RegionOverride property has a type of WARegions. Normally, the end result is an additional custom property displayed in the web part tool pane; in this case, the WARegions type is an enumeration and SharePoint therefore renders this as a drop down list:

Enumeration DDL

I say normally because today I was only getting a disabled DDL containing a single item ("Perth"). SharePoint was also kind enough to present me with this vaguely useful error message:

Some of the properties for this Web Part cannot be displayed properly. For more information, see your site administrator.

The WARegions enumeration is an existing enum I chose to reuse for the sake of convenience. Upon further inspection, I noticed the first element in the enum (Perth) was explicitly assigned a value of 1. Acting on my suspicion, I removed the explicit assignment and the DDL would then display as expected.

As a final test to confirm whether the explicit initialisation was somehow behind the single list item being displayed, I set default values for all elements in the enum but the error returned. Fortunately in my case I can do away with the explicit initialisation (which I'm not keen on—I prefer to initialise enums myself).

If you found this post helpful, please support my advertisers.

Friday, 3 December 2010

Relative URLs, the Rich Text Editor, and Reusable Content

We recently started leveraging the Reusable Content list to supply user-editable content to one of our interactive online forms and outbound emails. This allows the content owner to tweak the content as required as this content isn't locked away in custom code or resource files—no build and deploy required and the content management system is even used as such (whatdya know?!).

The Reusable Content list is provisioned by SharePoint when the Publishing feature is enabled; it allows users to define content snippets that can be maintained in a single location and updated automatically wherever they're used. You can optionally treat a reusable content snippet as a template—an editable copy is inserted into the page instead of a read-only, auto-updating view.

Reusable Content list items are pretty straightforward and, most importantly, contain a single HTML (rich text) field. SharePoint naturally displays its rich text editor around this field in edit mode so the edit user experience is similar to editing page content.

Unfortunately HTML fields in SharePoint are smarter than they should be and (in MOSS 2007), the product will mangle some content. I recently discovered it refuses to play with background-image style—they're silently removed whether they're inline or in an embedded stylesheet. (And yes, I know, inline styles are evil but this snippet was actually being plugged into an email so everything had to be self-contained).

Despite the tricks SharePoint plays on you with "managed" URLs, it seems the rich text field also stores URLs pointing to content within the current site as relative URLs. Absolute URLs are converted automagically but you'll never really see this until you pull the content out via the API.

We hunted for a way within SharePoint to convert all relative URLs in this content to absolute URLS but without much luck. I think there may be a Javascript function in one of the client scripts to do so for a chunk of content but there's nothing obvious in the server-side API.

To address this, we replace all relative URLs using the regex below (note the URL group) (is using regex to parse HTML bad? You be the judge). You may want to use SPUtility.GetFullUrl() to convert individual URLs.

@"(?:<\s*(?:a|img)\s+[^>]*(?:href|src)\s*=\s*[\""'])(?!http)(?<url>[^\""'>]+)[\""'>]"

If you found this post helpful, please support my advertisers.

Wednesday, 17 November 2010

How to determine if a SPPublishingPage is published

You can programmatically determine if the SPPublishingPage you're dealing with is in a published state by retrieving the SPFileLevel of the page's corresponding SPFile:

if (page.ListItem.File.Level == SPFileLevel.Published) …

You can also access this through the PublishingPage.ListItem.Level property.

The same SPFileLevel enumeration will also indicate if the page is in draft mode (checked in but not published) or checked out but in my experience this property will return SPFileLevel.Published or SPFileLevel.Draft more often than not.

To determine whether the page is checked out, use the SPFile.CheckOutStatus property in WSS 3.0 or the  SPFile.CheckOutType property in SP2010. Any value other than None means the page is checked out.

If you found this post helpful, please support my advertisers.

SPSite.AllWebs vs SPWeb.Webs

There's a subtle distinction between SPSite.AllWebs and SPWeb.Webs to be aware of—and yes, despite this being the SharePoint API we're talking about, it's more than just a naming oddity!

SPSite.AllWebs returns the immediate child webs of the site collection and all of their child webs (in other words, all webs recursively within the site collection); SPWeb.Webs has a much narrower scope and only returns the first level child webs immediately below the SPWeb object.

So what does this mean and how does it benefit you? If you're writing code that recursively walks the site hierarchy using SPWeb.Webs, you may be able to avoid the overhead by simply using SPSite.AllWebs. Recursion is fun and all but it adds complexity where it may not be warranted.

If you found this post helpful, please support my advertisers.

Friday, 15 October 2010

Must-know SharePoint debugging tips

This post is a follow-on to my Must-Know Visual Studio debugging tips article; I'm separating out my SharePoint debugging tips focused on list, feature, and solution deployment that don't relate to Visual Studio.

Here you go:

  • Try activating your feature without the –force attribute; you'll likely need to deactivate the feature first
  • Try uninstalling and reinstalling the feature
  • If something stuck and rebooting seems to clear some kind of cache deep inside SharePoint, stop IIS and restart the various SharePoint Windows services

@echo off
@echo Stopping services...
iisreset /stop /noforce
net stop "Windows SharePoint Services Timer"
net stop "Windows SharePoint Services Administration"
net stop "Office SharePoint Server Search"
net stop "Windows SharePoint Services Search"
net stop "Windows SharePoint Services Tracing"
@echo Starting services...
net start "Windows SharePoint Services Tracing"
net start "Windows SharePoint Services Search"
net start "Office SharePoint Server Search"
net start "Windows SharePoint Services Administration"
net start "Windows SharePoint Services Timer"
iisreset /start
@pause

  • Create a new list from your list definition (or at least, a new list item)
  • Rebuild your solution and redeploy
If you found this post helpful, please support my advertisers.

Thursday, 14 October 2010

The Mysterious SourceID Attribute

I see a lot field definitions around that unnecessarily include a SourceID attribute:

<Field
SourceID="http://schemas.microsoft.com/sharepoint/v3"

More often than not, the value of this attribute is set as above. Microsoft's own documentation samples tend to include this attribute.

So what does it do? Not a lot as far as I can tell; I don't include it with my own field definitions because inspecting the field after deployment with SharePoint Manager proves SharePoint sets it automatically with the GUID of the list that created the field. The Field Element documentation also indicates it is optional.

If you're intent on using it, the documentation describes this attribute as "containing the namespace that defines the field, such as http://schemas.microsoft.com/sharepoint/v3" so rather than use the default namespace it may be advisable to use your own (I haven't tried this myself).

If you found this post helpful, please support my advertisers.

Wednesday, 13 October 2010

SharePoint and Chrome - Better Together

I've been using Google's Chrome browser since its first release in 2008; I've loved nearly every second of the experience. Who would've thought there was room left to innovate in the browser space? Chrome's omnibar and rapid-fire JavaScript rendering, among other tweaks, are simply light years ahead of the competition.

While I normally rely on IE for my MOSS/SharePoint editing interactions, as of late I'm making the switch to Chrome in that space as well. What I've found to date has blown my mind.

Yes the MOSS 2007 UI degrades somewhat but it's still very useable. More importantly, Chrome drastically reduces the time it takes to accomplish basic tasks like modifying page content or viewing list data. I'm not saying these are normally slow in SharePoint but they can be in the www.westernaustralia.com environment (it's an ageing site with a lot of content and a lot of customisations); some pages in particular nearly grind to a halt in IE8 with the corresponding process consuming upwards of 1GB of memory the more I interact with the page.

Chrome "fixes" many of these slowdowns I'd previously attributed to the SharePoint environment and gives me all the Chrome goodness I've come to love over the last two years. It almost makes the SharePoint editing experience pleasurable!

If you found this post helpful, please support my advertisers.

Monday, 6 September 2010

.NET 4.0 application compatibility

Looks like those of us running SharePoint may be stuck on .NET 3.5 SP1 for a while longer but that's no excuse not to have .NET 4.0 installed side-by-side for use by other apps. One of the guys on the team, for instance, recently updated the custom crawler we run and needed pull in some of the Entity Framework features only made available in the 4.0 release; of course Microsoft will tell you the various .NET runtimes can be installed to run side-by-side and we managed to get away with a lot during the whole 2.0/3.0/3.5 onion thing.

Of course this all gets interesting very quickly when you've got a console application built against 4.0 that references assemblies (i.e. the SharePoint assemblies) built against version 2.0. Naturally everything worked fine in dev but as soon as we hit UAT, this exception cropped up:

An error has occured: Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information. [sic]

To address this, a simple .config element was added:

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
  </startup>

Notably, the useLegacyV2RuntimeActivationPolicy="true" attribute was required and we found this out the hard way by not having in place initially (dodgy internet code most likely!).

If you found this post helpful, please support my advertisers.

Sunday, 29 August 2010

Moving files in Visual Studio and the SharePointProjectItem.spdata file

While mucking around the CKS:Development Tools Edition Visual Studio 2010 extension for SharePoint, I inadvertently created a new master page from the Starter Master Page (CKSDev) template in an existing VS module:

Module-CKSDev-MasterPage

Neither VS nor SharePoint seemed to object to this arrangement—likely because the module in question was otherwise empty—but, as an experiment, I decided to move the CKS:Dev master page SPI into the project (and the top-level MasterPages module) nonetheless, making the nested CKS:Dev container obsolete.

This wasn't a good idea because subsequent attempts to build the package failed with the error "Could not find the file 'c:\…PageLayouts\StarterMasterPage1.master". Omg, what to do and why is trying to find these files in the project root?! I briefly pined for the good ol' .ddf file from 2007 days and then slapped some sense back into myself.

To begin fixing the problem, I first had to click the Show All Files button in the Solution Explorer window to reveal the SharePointProjectItem.spdata file in the root-level module (note the CKS:Dev inner master page module has its own .spdata file, which is empty after the move):

SharePointProjectItem_spdata

This is an XML file and its structure is quite basic; I'd describe it as a mini project file for the module in question as it lists the source files in the module along with their targets, and types.

Interestingly, the Source values specified all started with "..\" but inspecting a similar file from an un-modified module simply listed each file names; I assume Visual Studio helped out during the move to add this additional path information.

Removing the "..\" prefix fixed the problem and suggested to me the SharePoingProjectItem.spdata file may prove fragile during project refactoring. Sure enough, moving the files back to their original home added a spurious MasterPages\ prefix to each file and wreaked havoc all over again.

Moral of the story: manually edit the SharePointProjectItem.data file as required but beware Visual Studio may "fix" any of your changes.

One other thing to note: when I moved my files around, Visual Studio reset the properties on some of them. Most notably, Elements.xml file reverted to a Deployment Type of ElementFile instead of ElementManifest and the Build Action was set to Content. Modifying these properties also added the parent directory prefixes back to my .spdata file. Perhaps removing files from one location and adding them them back through the Add –> Existing Item… dialog is a safer way to approach this long-term if VS is going to continue meddling with things.

And just to add to the joy, one final bit of fun: a page layout added to the module in question was deploying fine; for the sake of producing the screenshot above, I excluded it from the solution, which also removed it from the files listed below the MasterPages module in the visual feature designer. While I assumed the feature designer would automatically update itself when I added the layout back to the module, I was wrong: the designer refused to update to reflect the inclusion of the page layout until I restarted Visual Studio! If this is a VS 2010 bug, it'll be one of several I've already filed with MS! Viva SP1!!!

If you found this post helpful, please support my advertisers.

Thursday, 19 August 2010

Setting the Visible property on a webpart throws

Using myWebPart.Visible = false and getting this?

The Visible property cannot be set on Web Part 'your_web_part'. It can only be set on a standalone Web Part.

Use myWebPart.Hidden = true instead.

If you found this post helpful, please support my advertisers:

Thursday, 12 August 2010

Configuring the People Picker and No exact match was found

SharePoint's people picker is generally one of those things that just kind of works—and fairly well no less. There's a gotcha to that statement however: the people picker works well when the the SharePoint farm exists in the same domain as the users you want to match against, or exists in a second domain where a two-way trust has been established with the first domain.

SharePoint-People-Picker

If your SharePoint web frontends exist in another domain with a one-way trust, you've got some extra work to do. This is the case with our dev setup and DMZ setup: our dev VMs are joined to the dev domain which trusts the corporate domain and our production servers are similarly joined to a web domain that also trusts the corporate domain. Both trusts are one-way.

I'll simplify what needs to be done by stating you simply need to tell SharePoint which forests or domains house the users you're after and provide an account from that domain.

Firstly you'll need to provide a key that will be used to encrypt any passwords you plug in during the next step. Run this on every WFE where "key" is a string of your choosing:

STSADM.exe -o setapppassword -password key

Next, set the peoplepicker-searchadforests property:

STSADM.exe -o setproperty-propertyname peoplepicker-searchadforests -propertyvalue <Valid list of forests or domains> -url <URL of the Web application>

where <Valid list of forests or domains> might look like this:

"domain:mydomain.com,mydomain\myuser,mypassword"

Supply the URL of the web application you want to configure (and note you don't need to set this for Central Admin, set it for a specific web application). Multiple domains and forests can be listed if necessary.

For more information, check out these resources:

 
If you found this post helpful, please support my advertisers:

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.

Wednesday, 23 June 2010

Use BackConnectionHostNames instead of DisableLoopbackCheck in production

If you're running SharePoint, you may have come across advice to configure the DisableLoopbackCheck registry key if you're running Windows 2003 SP1 and above and/or .NET 3.5 SP1.

Adding the DWORD will certainly keep you up and running and avoid search/crawl errors like the below but it's not the way to be doing in a production environment, despite popular belief.

Access is denied. Verify that either the Default Content Access Account has access to this repository, or add a crawl rule to crawl this repository. If the repository being crawled is a SharePoint repository, verify that the account you are using has "Full Read" permissions on the SharePoint Web Application being crawled. (The item was deleted because it was either not found or the crawler was denied access to it.)

What you should be doing in production is configuring specific sites by name using the BackConnectionHostNames Multi-String Value below HKEY_LM\system\CurrentControlSet\Control\LSA\MSV1.0. The Microsoft KB article isn't clear about the format this value should take but I've found adding each site without the scheme on a new line works.

Here's an example:

intranet1.site.com
intranet2.site.com

Bob Fox additionally suggests adding a new DWORD named DisableStrictNameChecking with a value of 1 to HKEY_LM\system\CurrentControlSet\Services\Lanmanserver\parameters and rebooting to avoid having to reboot every time a new site is configured. I got away without rebooting at all by simply restarting the IISAdmin service.

Saturday, 19 June 2010

Don't use XmlRoot with .webpart files

Just noticed using an [XmlRoot("MyRoot")] attribute on my webpart class in conjunction with a .webpart file (as opposed to a .dwp file) gives me this error when attempting to add the webpart to a page:

Incompatible Web Part markup detected. Use *.dwp Web Part XML instead of *.webpart Web Part XML.

Incompatible_web_part_markup_detected

Well I don't want to use *.dwp Web Part XML so I removed the XmlRoot attribute instead. I'm feeling wild, you see.

Beware the old doco: http://msdn.microsoft.com/en-us/library/dd584160(office.11).aspx 

Monday, 7 June 2010

Australian Weather Data (RSS…)

Despite my best efforts, I've been disappointed in my (repeated) attempts over the last twelve months to locate a free Australian weather feed I can consume via RSS and present as I wish. The Bureau of Meteorology is operating in the dark ages when it comes to the distribution of its authoritative weather data, Yahoo! weather—although available in a nicely structured XML document—is not available for commercial use, and all other feeds available at the time of writing send down HTML-formatted data with very limited rights for modifying the HTML itself.

Naturally, this is all very frustrating, especially when your client's intranet site needs weather for three specific towns in the middle of the outback or you're trying to build a redistributable weather widget (SharePoint web part anyone?).

Having given up on a free RSS feed, I returned to the BoM site as they do provide weather data via FTP (they also provide registered users access to additional services for a subscription fee). The terms are a bit unclear about distribution and attribution so I'll leave them up to you to interpret. This post presents my approach to accessing and parsing the basic (free) weather data file. A bit more work is involved than simply pointing a nice XDocument at an RSS feed URL and LINQ-ing away but hey, lower-level stuff is always fun and educational ;-)

By the way, this code will eventually make its way into SharePoint web part; if you're interesting in licensing this web part for use on one of your own sites, please drop me a line – info@mediawhole.com.

Access

The BoM provides weather data in the form of .dat, .txt, .html, and other file types via anonymous FTP. The .dat file is what we're after and for this example, I'll be using the IDA00006.dat file (Western Australia).

.dat files contain forecast information by town with details separated by a hash (#) character. The first line in the file describes the file format so if you get lost is a sea of hashes, start there. The Precis Forecast Products page provides formal documentation, of a sort.

Since the .dat file is just a text file we'll be using the WebClient class to manage access to the FTP location and open a readable stream over the .dat file URL:

WebClient client = new WebClient();

using (Stream data = client.OpenRead("ftp://ftp2.bom.gov.au/anon/gen/fwo/IDA00006.dat";))
{
}

Parsing

With access to the file contents, we can now parse it for easier access. To do so, we'll use a StreamReader to enumerate the file, tokenize each line, and store the results in a generic list:

using (StreamReader reader = new StreamReader(data))
{
    List<List<string>> weatherData = new List<List<string>>();
    string currentLine;

    while ((currentLine = reader.ReadLine()) != null)
    {
        string[] tokens = currentLine.Split('#');
        weatherData.Add(tokens.ToList<string>());
    }
}

It's worth pointing out the nested list structure I'm using here in place of a dedicated collection type. Each line in the file represents data for a single town; individual data elements within each line are separated by a hash character. To accommodate this, I tokenise each data element using the String.Split() method after reading the next line and store those elements in a nested list; each outer list item therefore contains data for a single town.

If that sounds at all complicated, here's a picture of the end result:

NestedListsThe parsed data is finally cached (not shown) for subsequent access. Notably, the BoM doesn't specify any limits on access attempts per day or an effective TTL value as do many RSS feeds. There's nothing stopping you from retrieving this data with every page load but your site will likely slow down and you'll also be guilty of gumming up the intertubes. You may also run the risk of being blocked from accessing the site.

Data Extraction

The final step in this process is locate and extract the weather data you're after (most likely for a specific town). I use the List<T>.Find() method, supplying a predicate that matches on town name, to accomplish this:

List<string> town = weatherData.Find(currentTown => currentTown[WeatherIndex.Location].ToLowerInvariant() == "perth");

Final Touches

The free data doesn't provide any form of condition code so associating a forecast (e.g. "Mostly sunny") with an icon is going to be tricky. I'm hopeful the BoM uses standard phrases that can be matched (or at least partially matched as a fallback (e.g. contains "sun") against known terms for hookup to generic icons but this could become a maintenance problem.

But that's it, weather!