February 2009 - Posts

Last week, I demonstrated how to call the Enterprise Search web service to get search results to a colleague.  I pointed him to my post on querying a web service and he was able to easily put the code together to call the web service.  However, when his code called the service, he got the following error.

<ResponsePacket xmlns="urn:Microsoft.Search.Response">

    <Response domain="QDomain">

        <Status>ERROR_SERVER</Status>

        <DebugErrorMessage>System.ArgumentNullException</DebugErrorMessage>

    </Response>

</ResponsePacket>

When I tried my code to call the web service, everything worked fine.  After a bit of troubleshooting, I remembered him saying that he was going to specify the web service address by IP because he wanted to call the web service over the VPN and DNS hasn’t worked so well lately over it.  I immediately though to have him change to the server name (as I have experienced other issues with IP addresses in the past) and everything immediately began to work.  The lesson to be learned is again, never try to access a SharePoint server by IP address.  Although some things might work, many things don’t.  This might be solvable with an alternate access path, but I find it easier just to avoid the IP thing all together.

There seems to be a huge plague of the following error sweeping through the SharePoint community right now.  This occurs when you go to the Search Settings page on your SSP.  You will probably notice it before this by a user trying to search and getting an error.  Here is the text.

The search service is currently offline. Visit the Services on Server page in SharePoint Central Administration to verify whether the service is enabled. This might also be because an indexer move is in progress.

It’s also been sweeping through the forums.  Here are two posts about it just in the last couple of days.

http://social.msdn.microsoft.com/Forums/en-US/sharepointsearch/thread/da8d8bdc-5788-4992-8245-b98d5341acf8

http://social.msdn.microsoft.com/Forums/en-US/sharepointsearch/thread/17a12447-7c77-492c-a7b9-2269b81cfafa/?ffpr=0

First, let’s start by saying that when this happens to most people that a) the search service is most certainly online and b) an indexer move is not in progress.  Google as much as you want, I have yet to see a concrete solution that didn’t result in some poor soul having to rebuild a search index.  I have had two clients experience this issue since the beginning of the year, plus I have had other SharePoint consultants tell me of similar horror stories.  I think the ultimate cause of the issue may be security related, as there have been some mixed results saying giving your crawl account administrator privileges fixes it (however, you never want to run like this).

So how do you fix it?  Well as I implied, I do not know of a way to fix it which allows you to keep your search index.  This part is mainly hearsay, but one of my clients was instructed by Microsoft support to rebuild their SSP.  This did in fact fix it, but it also meant I had to reconfigure everything in the BDC as well as the Search content sources, scopes, managed properties, etc.

Another slighlty less painful method is to stop the Office Search Service in Central Administration.  When I say this, I mean clicking stop in Central Administration which deconfigures the service, not by stopping the service in the Services control panel.  Then, you reconfigure the Office Search Service with a new database.  Again you have to reconfigure all of your search settings, but you don’t lose any BDC, Excel Services, and other items.

UPDATE: I was talking about this issue with a client and he was told by Microsoft support that this can occur when you have a farm and some of the servers aren't all on the same patch level in the farm.  In this case Infrastructure Update was applied on some servers, but not all of them.  I haven't confirmed this myself, but this does make a lot of sense and I have seen other posts out there that seem to imply this.  I would certainly check this first before rebuilding your search service.

I am just trying to bring visibility to this issue, because honestly I am tired of reconfiguring SSPs.  This issue is way out of hand right now and I really think it needs to be addressed by Microsoft. 

Yesterday, I posted on how to write a feature receiver to delete files it creates on feature deactivation.  This concept can easily apply to the deletion of web parts from the web part gallery as well (although the changes I describe here could work for anything deployed at the site collection level).  To make this happen, I created a new feature receiver with a FeatureDeactivating method which handles an SPSite object as opposed to an SPWeb object.  It looks like this now.

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{

    using (SPSite currentSiteCollection = (SPSite)properties.Feature.Parent)

    {

        string elementsPath = string.Format(@"{0}\FEATURES\{1}\Elements.xml",

                SPUtility.GetGenericSetupPath("Template"), properties.Definition.DisplayName);

 

        DeleteFeatureFiles(currentSiteCollection, elementsPath);

    }

}

For the purpose of my example, I have an elements.xml file that adds a web part like the following.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Module Name="WebPartPopulation" List="113" Url="_catalogs/wp" RootWebOnly="TRUE">

    <File Url="TestWebPart.webpart" Type="GhostableInLibrary" />

  </Module>

</Elements>

I changed all other methods to use an SPSite object as opposed to SPWeb and then made one minor change to the DeleteModuleFiles method to use the URL instead of Name attribute.

private void DeleteModuleFiles(string moduleUrl, IEnumerable<XElement> fileList, SPSite currentSiteCollection)

{

    // delete each file in the module

    foreach (var fileElement in fileList)

    {

        // use the name attribute if specified otherwise use Url attribute (since it is required)

        string filename = (fileElement.Attributes("Name").Any()) ? fileElement.Attribute("Name").Value

            : fileElement.Attribute("Url").Value;

 

        // pass the moduleUrl if it has a value

        if (!string.IsNullOrEmpty(moduleUrl))

            currentSiteCollection.RootWeb.GetFile(string.Format("{0}/{1}", moduleUrl, filename)).Delete();

        else

            currentSiteCollection.RootWeb.Files.Delete(filename);

    }

}

With these changes, you have a feature receiver that should work to delete any .webpart file (or .dwp)  you add to the gallery.

UPDATE: I updated the DeleteModuleFiles method to look for the file in the Name attribute first and then the Url attribute for the name of the file.

Using the Module element of a Feature has always been a great way to deploy a file.  Unfortunately, the main issue with this method is that it doesn’t clean up after itself.  How many times have you seen a site like this when working with features and the Module element?

FeatureFail

This is because Feature Activation doesn’t remove anything.  The easiest way to take care of this is write code to either remove the web parts from the zones or even easier just delete the page and assume activating the feature again is going to recreate it.  Before, when I wrote code like this, it was always hard coded to delete a specific file or whatever, but now I have a better approach.  I am going to use LINQ to XML to iterate through the Elements.xml file of my feature, find any files that were created and then delete them.  Of course, you can do this without LINQ to XML, but it just makes it easier.

Before, we begin, consider a typical elements.xml file that looks like the one below.  It deploys two files named default.aspx.  One in the root and one in a folder called Test.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Module Name="TestModule" Path="" Url="">

    <File Name="default.aspx" Url="default.aspx" IgnoreIfAlreadyExists="FALSE" NavBarHome="True">

      <AllUsersWebPart WebPartZoneID="Right" WebPartOrder="1">

        <![CDATA[

                  <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" xmlns:iwp="http://schemas.microsoft.com/WebPart/v2/Image">

                        <Assembly>Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>

                        <TypeName>Microsoft.SharePoint.WebPartPages.ImageWebPart</TypeName>

                        <FrameType>None</FrameType>

                        <Title>$Resources:wp_SiteImage;</Title>

                        <iwp:ImageLink>/_layouts/images/homepage.gif</iwp:ImageLink>

                        <iwp:AlternativeText>$Resources:core,sitelogo_wss;</iwp:AlternativeText>

                  </WebPart>

                  ]]>

      </AllUsersWebPart>

    </File>

  </Module>

  <Module Name="TestModule" Path="" Url="Test">

    <File Name="default.aspx" Url="default.aspx" IgnoreIfAlreadyExists="FALSE">

      <AllUsersWebPart WebPartZoneID="Right" WebPartOrder="1">

        <![CDATA[

                  <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" xmlns:iwp="http://schemas.microsoft.com/WebPart/v2/Image">

                        <Assembly>Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>

                        <TypeName>Microsoft.SharePoint.WebPartPages.ImageWebPart</TypeName>

                        <FrameType>None</FrameType>

                        <Title>$Resources:wp_SiteImage;</Title>

                        <iwp:ImageLink>/_layouts/images/homepage.gif</iwp:ImageLink>

                        <iwp:AlternativeText>$Resources:core,sitelogo_wss;</iwp:AlternativeText>

                  </WebPart>

                  ]]>

      </AllUsersWebPart>

    </File>

  </Module>

</Elements>

I created a Feature Receiver as usual by inheriting from SPFeatureReceiver.  On the FeatureDeactivting event, I have the following code.  The code simply gets an instance of the web object and gets the path to the elements.xml file as described in my past post.

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{

    using (SPWeb currentSite = (SPWeb)properties.Feature.Parent)

    {

        string elementsPath = string.Format(@"{0}\FEATURES\{1}\Elements.xml",

                SPUtility.GetGenericSetupPath("Template"), properties.Definition.DisplayName);

 

        DeleteFeatureFiles(currentSite, elementsPath);

    }

}

The DeleteFeatureFiles method takes an instance of the web object and the path to the elements.xml.  I then use LINQ to XML to load the document and call another method to delete the files.  In this case, I am specifically interested in the Url attribute of any Module element and any child File element.  The Url attribute contains the subfolder that the file is in.  The result is a list of all modules and the Files element of each one.   I had to specify the SharePoint namespace on the root element (as expected), but also on the Files child element.  Nothing else required it strangely enough. 

private void DeleteFeatureFiles(SPWeb currentSite, string elementsPath)

{

    XDocument elementsXml = XDocument.Load(elementsPath);

    XNamespace sharePointNamespace = "http://schemas.microsoft.com/sharepoint/";

 

    // get each module name and the files in it

    var moduleList = from module in elementsXml.Root.Elements(sharePointNamespace + "Module")

                    select new

                    {

                        ModuleUrl = (module.Attributes("Url").Any()) ? module.Attribute("Url").Value : null,

                        Files = module.Elements(sharePointNamespace + "File")

                    };

 

    // iterate through each module and delete the child files

    foreach (var module in moduleList)

    {

        DeleteModuleFiles(module.ModuleUrl, module.Files, currentSite);

    }

}

The URL of the module and a collection of XElement representing each file is then passed to a method for deletion.  I then just iterate through that list of files for deletion.  Note, I did have to handle files in the root of the site differently.  I think I can probably clean that code up, but for now this works.

private void DeleteModuleFiles(string moduleUrl, IEnumerable<XElement> fileList, SPWeb currentSite)

{

    // delete each file in the module

    foreach (var fileElement in fileList)

    {

        // use the name attribute if specified otherwise use Url attribute (since it is required)

        string filename = (fileElement.Attributes("Name").Any()) ? fileElement.Attribute("Name").Value

            : fileElement.Attribute("Url").Value;

 

        // pass the moduleUrl if it has a value

        if (!string.IsNullOrEmpty(moduleUrl))

            currentSite.GetFile(string.Format("{0}/{1}", moduleUrl, filename)).Delete();

        else

            currentSite.Files.Delete(filename);

    }

}

This works great for me and I can now just use one FeatureReceiver to handle this on any feature that I deploy.  This can obviously be applied to other concepts.  I’ll finally write my post on how to remove items from the web part gallery next.

UPDATE: I updated the DeleteModuleFiles method to look for the file in the Name attribute first and then the Url attribute for the name of the file.

On Saturday, I had the opportunity to speak at SharePoint Saturday in Kansas City.   It was a fun event with a pretty good turnout and I had the opportunity to meet lots of new people.  I gave a new talk on Deploying Code in SharePoint.  The talk focused on walking you through building a solution package from scratch.  Slides and sample code are attached at the bottom of this post.  I was able to catch sessions from Matt Bremer, Dennis Bottjer / David McCullough, and Becky Bertram.  All in all it was very informative.  The facilities at Centriq are pretty good, although the one major thing lacking is a large room to handle the welcome / prize giveaways.  I also ran into David Walker, who told me that School of Dev / SharePoint Saturday Tulsa would be on 3/28 (I think).  

Here is a brief list of posts that I have written about various topics relating to deployment.

Thanks again to Becky Isserman for allowing me to speak as well as heading up the whole SharePoint Saturday event.

First of all I would like to say this tool has improved quite a bit since the last version.  However, I still feel that it has a long way to go before a SharePoint developer would actually consider using it.  The functionality I am targeting today is the web part functionality.  Before that, I'll point out some things I ran into before I could get the product to work at all. 

A lot of this is due to the environment I created, but it's worth pointing out in case someone has a familiar environment.  When I installed this particular VM, I chose the quick install to save time.  As you know this installs a version of SQL Server Express as well as creates Central Admin and your SSP for you running with the Network Service account.  This is where the first issue is.  The application pool running the VSeWSS web service has to be a member of the Local Administrators  group (Administrators if you are on a Domain Controller).  This builtin account cannot be added to that group (to the best of my knowledge), so you have to create a new account (which I prefer anyway) to run the application pool for Central Administration and the VSeWSS site.  I did all this and I went into the database and gave this account db_owner permission on the config and central admin content databases and for the most part everything works.  I can package a solution and deploy it (however I still get an error activating the feature).  This was at least enough to get to the point where I could test it.

Now about using the tool itself.  I really like the added options to Deploy, Package, and Retract Solutions added to the context menu of your solution.  These all seemed to work really well.  I was able to create a quick Hello World web part and deploy it.  I activated it manually and was able to view it immediately.  One thing, I am very appreciative of is the ability to choose between a Full Trust or Partial Trust web part when you create the project.  You still have to specify the Code Access Security settings yourself, but at least it is possible now.  I am also a fan of the new options to quick deploy directly to the bin folder and 12 hive.

As a SharePoint developer, I put a lot of files into solution packages.  Pages, XML files, Master Pages, Site Template Definitions, you name it.   I want to be able to put these files in my wsp.  I assumed WSP View would have this functionality.  You can get to it by going to View -> Other Windows -> WSP View.  Maybe, there is functionality there, but I couldn't figure out how to add any files to the package.  You have no direct access to the DDF file and there are no menu options to allow you to customize or add files in WSP view.  There is an option to add another feature, but you can't customize what files to add along with the feature.  Somehow, when you create a new web part project, the .webpart file shows up in the WSP view, but I have no idea how.  This by itself, means I can't use this tool.  Again, if I am just being stupid and can't find it, let me know.  The one thing I will note here is that you can customize the manfiest file here. 

Another thing, I found lacking is that I can only add one web part per project.  Sure, I can manually create another class, but I want it to generate the .webpart file for me and put it in the solution.

Features I would like to see:

  • Remote SharePoint Server support - Let me install it on a Windows XP machine (without WSS obviously) and specify the URL to my SharePoint server.
  • Ability to customize contents of WSP file
  • Better CAS support - A tool to help determine security policies would be great
  • Multiple web parts in one project
  • Better control of what goes into the solution package

I know the product is just a CTP, so hopefully, with some good feedback we can get a tool we can all use.  I guess for now its back to manual creation, stsdev, or WSPBuilder.  Microsoft has relied on the community too long for SharePoint tools, it is time they produce.

I have also cross posted this to the SharePoint Developer Forum.