September 2009 - Posts
Ever been on the features page and wondered what the Id of a Feature is?
The features page in the SharePoint UI is obviously a quick and easy way to activate and deactivate features. However, at first glance all you see is the title and description of the feature. You have no idea of knowing what the folder is let alone its Id. You’re out of luck on determining the feature folder name here, but you can get the Id pretty easily by viewing the source HTML of the page. I want to know the Id of the My Web Part feature, so I view the source and type in the name of the web part.
Looking at the source, we can quickly find what we need.
Take a look at the snippet of HTML above. This is where it displays the information for the title and description of my web part. Following that, you will see a non XHTML compliant div tag (really hope the HTML is better in the next version) with an id set to a GUID. As you might guess, that GUID is the Id of the Feature. It’s that simple. Just a quick tip today. Hope you find it useful.
I’ve been chasing this particular error for a little while and thought I would take the time to share my experience and how to resolve it. The scenario I had is I have a custom content type with several custom site columns. I generated the CAML for my content type and site columns using SPSource. When I clicked on the Create a new custom template button, I received the following error (or one similar).
propertySchema0.xsd#/schema/element[1][@name = 'properties']/complexType[1]/sequence[1]/element[1][@name = 'documentManagement']/complexType[1]/all[1]/element[1]
Reference to undeclared namespace prefix: 'ns1'.
As with all InfoPath errors, this is completely useless. After many attempts at trying to figure out this issue, I noticed that I could create a new Lookup Column and a new Content Type using the SharePoint UI and not receive this error. I then busted out the Imtech Fields Explorer and as far as I could tell all of the properties were just about the same. However, I noticed one difference in the SchemaXML property. My Field element did not have a SourceId but the column created through the UI did. It’s value was set to the Id of the web hosting the site column. I knew I couldn’t set it to that and so I remembered in the past I had always set the SourceId of the field element as seen below.
SourceID=”http://schemas.microsoft.com/sharepoint/v3”
This got me closer, the content types and site columns deployed correctly and the SchemaXML had a SourceID in it. However, when I tried to create a Document Information Panel this time, I got this new lovely error.
InfoPath has encountered an error. The operation failed.
Catastrophic failure
Once again, thanks InfoPath team for such useful error information. After further investigation, I discovered I needed to set the StaticName attribute as well. I simply set this to match the Name attribute I had and everything works now. It’s strange how the content type can work completely everywhere else, but in InfoPath it’s very picky. Here is what a complete working Field element might look like for you.
<Field Type="Text" DisplayName="MyField" Required="FALSE" MaxLength="255" Group="My Field Group" ID="{2E6858a5-6ae2-409f-8492-fa15c39821c8}" Name="MyField" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MyField" />
I mentioned above the issue I had was with a Lookup column but I quickly discovered that this was happening with all of my columns and had nothing to do with the lookup. SPSource has saved me a ton of time, but this is just another minor thing to look out with the tool. I logged an issue on CodePlex to see if we can get these two attributes added to the Site Column export. If you aren’t customizing Document Information Panels, then there is nothing to worry about with the exported CAML, but if you are be sure and add these two attributes.
According to Google Analytics, my introduction to SharePoint topics are by far the most popular topics on the site. I wanted to continue the trend on topics for new SharePoint developers, so I thought i would discuss debugging today. I’ve already discussed remote debugging in the past, but I thought it would be worth spending time on simple local debugging and provide some pictures. There may be several posts on this already, but I wanted something that I could refer to new developers I am working with.
In the ASP.NET world, you are used to working with everything on your own machine, setting a breakpoint, hitting F5 and you start debugging. We can do it the same way with SharePoint development right? No, of course not.
Before we start, lets assume you built a web part and want to debug it. If you aren’t familiar with how to build and deploy a web part, be sure and take a look at my post. In the scenario we are talking about, you are most likely developing inside a virtual machine and it is hosted on your other computer or somewhere else. This virtual machine has Visual Studio installed.
.PDB Files
The way you debug varies slightly by whether or not you are deploying your binary to the GAC or to the bin folder. If you are deploying to the GAC, you do not need to worry about the .PDB file as Visual Studio will magically find it on its own when you start debugging. However, if you are deploying to the bin folder, you must copy the .PDB file from your bin\debug folder and copy it to the bin folder in inetpub for you web application.
Start Debugging
At this point you are ready to start debugging. First, I’ll start by marking a breakpoint in my code.
As I mentioned earlier though, you can’t just hit F5 and call it good. Instead, you have to attach to the process running the web application your web part is on. This is the same process used when remote debugging. The difference is you don’t have to run the remote debugger and tell Visual Studio to connect to a remote server. to do this, go to the Debug menu and choose Attach to Process. You will then get a screen that looks like the one below.
The first thing you need to do is check the Show processes from all users and Show processes in all sessions checkbox. Don’t worry about the Transport, Qualifier, or Attach to options. The default options should work for you. If you happened to change them in the past, use the above screenshot, to get you back to where you need to be.
Attaching to the Worker Process
What we want to do is attach to the w3wp process that is running our web application. We know this is a w3wp.exe process, but if you look at the screenshot, you can see that we have three w3wp.exe processes in the list. How do you know which one to use? Well you have a number of options. You can use brute force and try each one and hit your web page and see if the breakpoint hits. That’s not really an ideal solution. If you’re smart when you set up your environment, you might have created separate accounts for each application pool. Notice above, how we have accounts named MOSS_AP_SSP, MOSS_AP_Portal, and MOSS_AP_CentralAdmin. Obviously, I am not debugging the SSP or Central Admin, so the MOSS_AP_Portal is the account to use in my case.
I get it though. That is an ideal situation and you may not be lucky enough to have everything running on separate accounts, so we need an alternative solution. Luckily, IIS has a script you can run which will tell you which worker process is running on which port. My web application is running on port 80, so if we identify a worker process for that port, we are in luck. In Windows Server 2003, you run cscript iisapp.vbs. In Windows Server 2008, you run appcmd list wps. Here is what it looks like in Windows Server 2008.
Now, I can confirm the process I want to attach to is 5980. I’ll click on this process in the list and we can start the debugging process. If all goes well, your breakpoint should look good in Visual Studio and it won’t have an explanation point. If it still has an explanation point, you may just need to hit a page on the site first to get Visual Studio to load the assemblies for debugging. Try hitting the page that has your web part and if everything works correctly, your break point should be hit and look like this.
As an ASP.NET developer, this may be different than what you are used to, but the steps involved really aren’t that bad. The code that I debugged in this example is the same code used in the How to Build and Deploy a Web Part post. If your breakpoint doesn’t get hit, I recommend recompiling and trying again. Verify that you have the right worker process and copy the .PDB file out if you are deploying to the bin folder.
A few months ago, I discussed how to use LINQ to XML to parse your elements.xml file to delete any files that you may have deployed on feature activation. Today I have decided to reuse this concept to delete any lists that I have created when I deactivate a feature? Now you may ask, “why would I want to delete a list when a feature is deactivated? I’ll lose all of my data in that list!” My answer is: Yes, of course you will, but sometimes when building a feature to deploy a list(s), you want to delete the list each time before you deploy a new version of it. During development this is a huge time saver. Right now, I am working on a feature that deploys four document libraries. This means I have to manually delete each one. That is a huge waste of time, but what is nice is that we can reuse the concept above with LINQ to XML and delete lists instead.
The code is quite similar. Have a look. We first, write some code to get the path to the elements.xml file. This code snippet assumes, it is always named Elements.xml. Maybe in the future, I will have it look in the feature.xml file, get all of the ElementManifest definitions and then delete whatever it finds in each file. For now though, we assume, you can change the path as needed.
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);
DeleteLists(currentSite, elementsPath);
}
}
From here, it passes a reference to the current SPWeb and the path to the XML document. The path to the elements.xml file is used to populate an XDocument which we can then query with LINQ to XML.
private void DeleteLists(SPWeb currentSite, string elementsPath)
{
try
{
XDocument elementsXml = XDocument.Load(elementsPath);
XNamespace sharePointNamespace = "http://schemas.microsoft.com/sharepoint/";
// get each URL to each list
var listInstances = from module in elementsXml.Root.Elements(sharePointNamespace + "ListInstance")
select new
{
ListUrl = (module.Attributes("Url").Any()) ? module.Attribute("Url").Value : null,
};
// iterate through each list and delete it
foreach (var listInstance in listInstances)
{
try
{
SPList currentList = currentSite.GetList(string.Format("{0}/{1}", currentSite.Url, listInstance.ListUrl));
currentList.Delete();
}
catch (System.IO.FileNotFoundException e)
{
// this exception is thrown if the list does not exist
}
}
// update the site
currentSite.Update();
}
catch
{
}
}
This code is quite similar to that used in my other post, however instead of looking for Module elements we are looking for ListInstance elements. It creates a list of those Urls to each list and iterates through them. For each list it finds, it deletes the list. I’ve written code in the past to manually delete lists, I create by name. That works fine, but the name of each list has to be maintained. Now, I just attach this to any feature I am working on and it deletes any list (or lists) that I throw at it. More than likely this isn’t something you want once you go to production, but it will save you a ton of time during development.
Have you ever wanted to create a site collection off the root of your site at a path such as (/MySiteCollection), only to find a screen that looks like this?
This really stinks. After all, I don’t want my site new site collection to be under /personal or /sites. I want it to be under the root (/). The link to the left points us to the managed paths page, but you need to know what you are doing. Going to the managed paths page, we see something like this.
As you can see we have four entries listed, but only two are setup as Wildcard inclusions. Looking at the dropdownlist from the first screenshot, we can quickly put two and two together and realize that it has to be a wildcard inclusion to show up in the list. So at this point, it seems we need to change the (root) inclusion to be a wildcard. However, there is no edit button, so this leaves us with no choice but to delete it. We can then add the root as a wildcard inclusion as shown below.
After adding the managed path, we can now go back to the Create Site Collection page and we see that the root path is now available.
Excellent, we can now create our new site collection with the path off of the root that we wanted. Complete the rest of the information on the page, and we now have a working site collection. We test the new url http://moss-server/MySiteCollection and things work great. Now, I want to go back and check something on the root site collection. Wait, what’s this?
404 Not Found. That’s a bad time. Remember those changes to our managed paths? SharePoint won’t serve up the root site collection with it set as wildcard inclusion, but it will server up anything underneath it. This means we have to go back to our managed paths and delete the wildcard inclusion off of the root and change it back to an explicit inclusion.
Excellent, that fixed our root site collection, but is our new site collection working? Of course not!
Now what do we do? Well when you think about it, it does make sense. We need a managed path defined for our new site collection since we deleted the wildcard. Go create a managed path for the new site collection with an explicit inclusion. Here is what the complete site collection lists looks like when we are done.
If we test both the root and new site collection, we will find that they both now work. It’s kind of a pain isn’t it? Hopefully, these detailed steps will help you the next time you need to create a site collection off of the root site (or anywhere else for that matter).
It’s a known fact that MOSS 2007 Service Pack 2 hates me. Today, I was excited when I upgraded a server to SP2 and the configuration wizard didn’t fail. I was even more excited to see that my SSP and Central Administration still worked after the upgrade as well. Unfortunately, when I hit my main web application, I got a yellow screen with the following error.
COMException (0x80004005): Cannot complete this action.
Please try again.]
Microsoft.SharePoint.Library.SPRequestInternalClass.GetFileAndMetaInfo(String bstrUrl, Byte bPageView, Byte bPageMode, Byte bGetBuildDependencySet, String bstrCurrentFolderUrl, Boolean& pbCanCustomizePages, Boolean& pbCanPersonalizeWebParts, Boolean& pbCanAddDeleteWebParts, Boolean& pbGhostedDocument, Boolean& pbDefaultToPersonal, String& pbstrSiteRoot, Guid& pgSiteId, UInt32& pdwVersion, String& pbstrTimeLastModified, String& pbstrContent, Byte& pVerGhostedSetupPath, UInt32& pdwPartCount, Object& pvarMetaData, Object& pvarMultipleMeetingDoclibRootFolders, String& pbstrRedirectUrl, Boolean& pbObjectIsList, Guid& pgListId, UInt32& pdwItemId, Int64& pllListFlags, Boolean& pbAccessDenied, Guid& pgDocId, Byte& piLevel, UInt64& ppermMask, Object& pvarBuildDependencySet, UInt32& pdwNumBuildDependencies, Object& pvarBuildDependencies, String& pbstrFolderUrl, String& pbstrContentTypeOrder) +0
Microsoft.SharePoint.Library.SPRequest.GetFileAndMetaInfo(String bstrUrl, Byte bPageView, Byte bPageMode, Byte bGetBuildDependencySet, String bstrCurrentFolderUrl, Boolean& pbCanCustomizePages, Boolean& pbCanPersonalizeWebParts, Boolean& pbCanAddDeleteWebParts, Boolean& pbGhostedDocument, Boolean& pbDefaultToPersonal, String& pbstrSiteRoot, Guid& pgSiteId, UInt32& pdwVersion, String& pbstrTimeLastModified, String& pbstrContent, Byte& pVerGhostedSetupPath, UInt32& pdwPartCount, Object& pvarMetaData, Object& pvarMultipleMeetingDoclibRootFolders, String& pbstrRedirectUrl, Boolean& pbObjectIsList, Guid& pgListId, UInt32& pdwItemId, Int64& pllListFlags, Boolean& pbAccessDenied, Guid& pgDocId, Byte& piLevel, UInt64& ppermMask, Object& pvarBuildDependencySet, UInt32& pdwNumBuildDependencies, Object& pvarBuildDependencies, String& pbstrFolderUrl, String& pbstrContentTypeOrder) +215
That’s just great I figured. I checked the logs folder in the 12 hive and all I could find was an entry with the same text “Cannot complete this action”. Quite useless. After doing some searching, I encountered the following KB article. This articles applies to WSS2, but I noticed a lot of things mentioning the word impersonation, so I decided to have a look in my web.config. Sure enough, this server had impersonation turned off for some reason (no telling why). I set it back to true and the error went away.
I can’t continue to reiterate how much time SPSource can save you when it comes to exporting existing lists into usable features, but I found one minor thing to look out for when you are exporting lists. Usually, you can take just about whatever SPSource gives you and redeploy it without having to change a thing, however in the cases of lists you need to look out for the TemplateType attribute otherwise it can cause you problems. Take a look at the XML below from the elements file.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ListTemplate AllowDeletion="True" AllowEveryoneViewItems="False" BaseType="1" DisplayName="Concepts" Description="" EnableModeration="False" Hidden="False" OnQuickLaunch="True" Name="List1" Type="100" SecurityBits="11" Sequence="110" DocumentTemplate="101" VersioningEnabled="True" />
<ListInstance FeatureId="cac9f905-9380-4252-8307-d2cffb6f0c1f" Id="c5dd284f-fb59-44b9-bcfd-b4f779d9fd8d" TemplateType="100" Title="List1" Url="Lists/Lists1" />
<ListTemplate AllowDeletion="True" AllowEveryoneViewItems="False" BaseType="1" DisplayName="Regionals" Description="" EnableModeration="False" Hidden="False" OnQuickLaunch="True" Name="Regionals" Type="101" SecurityBits="11" Sequence="110" DocumentTemplate="101" VersioningEnabled="True" />
<ListInstance FeatureId="cac9f905-9380-4252-8307-d2cffb6f0c1f" Id="631657f0-b687-4b61-ad28-13ea1140cd21" TemplateType="101" Title="List2" Url="Lists/List2" />
</Elements>
As you can see I have a ListTemplate and ListElement element for two different lists (list1 and list2). If you look further you notice that the Type attribute on the ListTemplate elements is 100 and 101 respectively. The issue is that 100 is already reserved for a generic list and 101 is a document library so deploying this file can cause general weirdness in SharePoint. Here is the list of stock list template IDs in case you were curious.
As you may guess this is pretty easy to resolve. Just pick a new number that is not reserved for your Type. I believe the best practice recommends starting at 10000. After you change the Type attribute, be sure and change the TemplateType attribute of the ListInstance element to match it. That is all there is to it. If you haven’t checked out SPSource yet be sure to, the next time you need to export sites, lists, content types or site columns. It can save you a ton of time.
This week, I needed to deploy lookup columns to some of my lists and as usual I wanted to avoid writing code at all costs. As some of you may know, Kyle Kelin and I debate this topic often as he prefers a code approach. I figured it had to be possible with CAML, but many claimed it was not even possible. A few approaches showed up out there involving using code to modify the elements.xml file with your GUID, but that just wasn’t going to cut it for me. One popular post on the topic by Josh Gaffey, started me in the right direction, but there were a few hurdles I ran into as I was trying to implement it. It would create the list, show the content type, and site columns, but when I tried to create a new item, the lookup column was not there. The basic technique is that you specify the path to the list in the form of (Lists/MyListName) in the List attribute of the Field element in both your schema.xml file of your document library template as well as the definition of the site column. The first thing I learned here is that you cannot simply omit declaring site columns and a content type out and go with list level columns. It simply will not work (no idea why).
Here is how I ended up getting everything to work. I created my initial lists, content types, and site columns through the SharePoint UI. I then used
SPSource (seriously a great tool) to export the content type, site columns, and lists (both the source list and the one that contained the lookup column). My goal was to deploy these items to another site collection. I recommend creating separate features for your content types and the list definition. This way you can ensure the content type is deployed first. To export your content type / site columns, create a new stub feature.xml file as described on their site and then create an .spsource file with just the content type in it like this (no need to specify columns).
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType Name="Test" />
</Elements>
In my example, my content type was called Test. Create a separate feature folder for your list template and instance. Create another feature.xml and an .spsource file for the list which looks something like this. My list is also named Test in this case.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ListTemplate Name="Test" />
</Elements>
SPSource does a good job when it exports my list definition and properly specifies the list using a path instead of a guid in my lookup column as shown below (from schema.xml).
<Field Type="Lookup" DisplayName="DocumentCategory" Required="FALSE" List="Lists/Categories" ShowField="Title" UnlimitedLengthInDocumentLibrary="FALSE" Group="My Group" ID="{9266e0fa-ae49-438c-acdc-73063684ac8f}" SourceID="{50b253e5-e90e-4781-8fce-cece417b185e}" StaticName="DocumentCategory" Name="DocumentCategory" Customization="" ColName="int1" RowOrdinal="0" />
We don’t actually have to change a thing in this file. What I did have to change is the List attribute of the file it generated for my site columns. As Josh’s post above stated, we have to change the List attribute from a GUID to the path of the list. His post also mentioned you might want to set the PrependId attribute but I have since learned that this is only used when you are using the LookupMulti type (although I can’t remember the source I got this from).
<Field Type="Lookup" DisplayName="DocumentCategory" Required="TRUE" List="Lists/Categories" ShowField="Title" UnlimitedLengthInDocumentLibrary="FALSE" Group="My Group" ID="{9266e0fa-ae49-438c-acdc-73063684ac8f}" Name="DocumentCategory" />
At this point you can deploy your site columns, content type, and then custom list (in that order) and if all goes well you will be able to create a new item of that content type with the lookup column functioning. One thing to note, if you get this wrong, your list will behave oddly. One thing I noticed is that it won’t turn on content types for the list when that happens. If this is happening go back and check your work. Another thing people mentioned is that the source list for the lookup column has to exist before you create the lookup column. This in fact is not true. It will work and it will display a drop down list for the lookup column, but it will be empty of course since the list does not exist.
Along the way I figured out a few other things. When I was trying to figure this out, I was trying to just create the content type and then manually add it to the list via the UI. Doesn’t work. If you try to add a content type with a lookup column defined in this way to an existing list, you will get the following error.
Exception from HRESULT: 0x80040E07 at Microsoft.SharePoint.Library.SPRequestInternalClass.AddField(String bstrUrl, String bstrListName, String bstrSchemaXml, Int32 grfAdd)
at Microsoft.SharePoint.Library.SPRequest.AddField(String bstrUrl, String bstrListName, String bstrSchemaXml, Int32 grfAdd)
The only way to get the content type associated with the list is via CAML when it is created. Another odd thing is that the lookup column never shows it is bound to the other list correctly. Here is what the source lookup column looked like.
Notice where it says Get information from, the name of the list is present. Here is what my copy of the list looks like.
Notice that the list name is not present. Strangely enough though, everything works fine in the copy. This one through me for a loop for a while because it didn’t seem like things weren’t working and as I mentioned above I couldn’t add the content type to a list (nor could I add the column to a content type).
As you can see there are few oddities about deploying lookup columns in this manner, but it does work. @SPKyle informed me he could have written code to do this hours ago, but I am quite happy that I can deploy things in this manner now.
Twitter: @coreyroth