July 2007 - Posts

I found yet another instance where the documentation on Enterprise Search was purely incorrect. I won't go into the whole process of how Enterprise Search works over a web service because for the most part it is correct. However, what is not correct is the schema you pass it. Here is what it is in the help file.

<QueryText language=xml:lang type='MSSQLFT'>
SELECT Title, Path, Description, Write, Rank, Size FROM Scope() 
WHERE CONTAINS(Description,'SharePoint')

Here is the XML that actually works.

<QueryPacket xmlns="urn:Microsoft.Search.Query" Revision="1000">
  <Query domain="QDomain">
      <QueryText language="en-US" type="MSSQLFT">
SELECT Title, Path, Description, Write, Rank, Size 
FROM Scope() 
WHERE CONTAINS(Description,'SharePoint')

So it is mostly correct, but nothing in there says to wrap your QueryText inside a QueryPacket element. I discovered this syntax from a book excerpt I found on MSDN. It has some decent information in it.

Customizing and Extending MOSS 2007 Search

Ok, well I started writing this post yesterday and somehow I accidently closed my browser and lost the entire contents of the post, so here I am at it trying it again. Lately, I have been working with a TreeView to handle hierarchical naviagation. Specifically I have been working with SharePoint's SPTreeView control which inherits from the ASP.NET version and functions roughly the same. One of the biggest flaws I have found in both controls is that when used with a SiteMapProvider, it does not expand thte tree to the level of the current page. I found this highly annoying because the user will never know where they are as they are navigating through the site. After much looking and not finding much, I came up with this solution after seeing an MSDN article that showed how to save the state of which nodes were open as a user navigated through the site.

The code for this is actually pretty simple. I created a method called ExpandTreeToCurrentPage that took a TreeNodeCollection as a parameter. This method will be called recusrively in the OnDataBind event handling method as we move through the tree. Then I just iterate through the TreeNodeCollection and compare the NavigateUrl to the AbsolutePath of the page (you may want to compare something else if you want to include query strings, etc.). If it matches I return true, which forces the recursion to stop. One thing I ran into is that ChildNodes will return a count of 0, unless the current node is expanded first. This makes things a little trickier, but not too bad. I just expand the current node before making a recursive call to the same method passing in the ChildNodes as a parameter. After the recursive calll, I make a call to collapse the current node. Here is what the code looks like.

protected bool ExpandTreeViewToCurrentPage(TreeNodeCollection treeNodes)
 // iterate through all nodes to find the current page
 foreach (TreeNode treeNode in treeNodes)
   // check to see if the value matches the current url               
   if (string.Compare(treeNode.NavigateUrl, 
         Page.Request.Url.AbsolutePath, true) == 0)
           // a match has been found so stop looking
           return true;

    // must be called in order to see child nodes 

    // recursively call this method to check the children
    if ((treeNode.ChildNodes != null) &&
          (treeNode.ChildNodes.Count > 0))
     if (ExpandTreeViewToCurrentNode(treeNode.ChildNodes))
        return true;

  // collapse any nodes that were opened earlier

// false will be returned if the node was never found
return false;

This may not be the best solution, but I have yet to find anything better. I think it is rediculous that this behavior isn't apart of the control by default. Since the MSDN article does something fairly similar, I have a feeling this is as good as it gets. If you have any other ideas, feel free to let me know.

Filed under:

I have basically followed the instructions straight from the SDK on the proper way to install a Master Page for global use in a site collection. However, I have found that there can be some issues when you do this when you start pushing out updates to your master page. The main reason is because it seems that once you instal a master page via feature, the chances of the system going and letting you delete it are slim. If it can't delete the master page, it will not be able to install anything in that feature or solution. Typically, the error you get is the following.

This item cannot be deleted because it is still referenced by other pages.

So naturally you start looking for pages that reference it. In fact though, just by installing the feature, you will probably get this error message. I ended up finding a hack (MS suggests hiding the master page, but that obviously is not a good work around). The solution is to copy the MasterPage into a new folder using SharePoint designer and then delete that folder. This will allow you to install a new feature with your changes to your master page.

Filed under:

Alright, so you managed to get some data in the BDC and you want to be able to enable Enterprise Search on it. Well this is one thing that the MOSS SDK actually has decent information. However, in my working experience I have found that a few things are missing. Hopefully, this will help you in getting a crawl working the first time.

Typically in a MOSS installation, you have multiple service accounts. Typically, your Enterprise Search service is going to have its own account. In this example, we'll just call it mydomain\EnterpriseSearchService. You will also have a centeral administration account most likely. In most cases I typically use this account with the BDC, because it seems to be the easiest to get to work. Since I am not a MOSS expert, I am telling you what I have gotten to work not what is necessarily best practice. Hopefully I am close, but I fully expect to get schooled one day on it.

Follow the instructions in the MOSS SDK on how to set up Enterprise Search first. Next, go into your application definition in your Shared Service Provider to manage permissions. Add the Enterprise Search and Central Administration account and check all the checkboxes with the various permissions. Also click the Copy all permissions to descendants button. This will copy these permissions down to the various child level objects in your schema. All elements except for extermely important one needed to make Enterprise Search work.

So how do you fix that? Modify your BDC schema file with an AccessControl element. For each entity you need an access control element giving permission to your central admin account and the enterprise search account. However, that's not all. It seems when you make a change like this it becomes the only permissions applied to that object, so if you do not also grant permission to yourself, you will never be able to delete that BDC entry again. Here is what the entries would look like. These lines with your appropriate accounts would go immeidately after the opening of your Entity element.

 <AccessControlEntry Principal="mydomain\moss administrators">
   <Right BdcRight="Execute"/>
   <Right BdcRight="Edit"/>
   <Right BdcRight="SetPermissions"/>
   <Right BdcRight="SelectableInClients"/>
 <AccessControlEntry Principal="mydomain\EnterpriseSearchService">
   <Right BdcRight="Execute"/>
   <Right BdcRight="Edit"/>
   <Right BdcRight="SetPermissions"/>
   <Right BdcRight="SelectableInClients"/>
 <AccessControlEntry Principal="mydomain\CentralAdmin">
   <Right BdcRight="Execute"/>
   <Right BdcRight="Edit"/>
   <Right BdcRight="SetPermissions"/>
   <Right BdcRight="SelectableInClients"/>

Once you have made the changes to your schema, reimport it and start your crawl. If all goes well, your BDC entity will now be indexed. Also, you need to be sure and grant SQL permissions to your Central Admin and Enterprise Search accounts. I also typically use the RevertToSelf setting on the AuthenticationMode element in your LobSystemInstance connection settings. Otherwise you have to deal with kerberos or setting up single sign-on.

Lastly, I found that some instructions were incorred in the Enterprise Search section of the downloaded CHM file from the SDK. So if things aren't making sense, check the online version of the page, because it looks like it has been corrected.

Ok, sometimes I post some really simple stuff. At least I think it should be simple. Today I was tasked with figuring out how to add a managed path in SharePoint using custom code. This is a fact a simple task, but knowing where to look for it is not. The first thing you need to know about the SharePoint object model is that it is all based on legacy crap that doesn't follow any naming conventions whatsoever (every object starts with SP). So knowing this, I knew I needed to create a managed path. I start by looking for stuff like SPManagedPath and SPPath, but this was to no avail. I then proceed to search google which as usual is just about useless when finding anything SharePoint related. Seems people have more questions than answers. This is why I make it a point to post everything I can on SharePoint so that other developers do not have to look so hard.

At this point, I was like well maybe I can find it using reflector. So I go and find the page that manages path in Central Administration and see that the file name is _admin/scprefix.aspx. That makes a light bulb go off in the head and I search the SDK documenation for SPPrefix and there it is. The object I needed. I got lucky today. Sometimes you have to use reflector and dig deeper. Hopefully, this lesson will help others out there on how to find things. Just about everything in WSS 3 has been called something different in WSS 2. It just takes time to learn what those changes are.

How do you add a managed path from code? Well, basically straight from the help file here. Surprisingly the help file actually had a code sample and a description of the object (this is very rare for most WSS objects).


That's it. You can also do SPPrefixType.ExplicitInclusion, if you don't want it to recrusively manage all of the paths underneath the one you specify.

Filed under:

Have you written something in SharePoint that is going to take a while? Trust me it is not hard to do. Ever wondered how they make the spinning gears shows up during a long operaton? Well in fact it is pretty easy. Simply create an instance of the SPLongOperation object (Microsoft.SharePoint.dll). Make a call to the BeginOperation method, put your code in and then make a call to EndOperation passing it a URL of what page to load when it is finished. That is all there is too it. Here is an example.

SPLongOperation longOperation = new SPLongOperation(this.Page);

// my long operation

Filed under:

Many of you have already heard me complaining about the tedious task of generating a schema for the MOSS Business Data Connector. Its a painful process and I always said there is no reason why it can't be automated. I wanted a product that I could simply point at a set of tables and it spits out the XML I need. Well today, I managed to stumble upon a product that does just that. The product is called BDC Meta Man and it allows you to drag and drop tables into a design surface, click a button and you have an XML file that is ready to import.

After I installed the product, I fired it up. There is a button that allows me to connect to an initial data source. The trial version allows SQL Server, but if you have the professional version you can also connect to Oracle and web services. I am particularly excited about the web service support, since that schema is pretty painful. Once I chose the data source, I simply dragged and dropped a few tables onto the design surface, told it where I wanted the XML file and it spit out a nice clean XML file for me to import.

Generating a file is the easy part, but now for the real test. Does it import and can I view the data? The only issue I ran into was I had to manually set the permissions to my SQL Server (this is mostly just due to an issue in the way my environment is set up). Other than that, I dropped a Business Data List on the page, picked the data source and low and behold, there was my data. That is amazing. This product is going to easily save me 2 to 4 hours per entity that I have to create.

The product is great. I have a couple of idea, that would make it excellent. The main thing I would like is for it to have the ability to save the connection I create as well as what objects I have dragged onto the design surface, so that if I ever need to regenerate the XML, I don't have to set it up again. Admittedly, I only have the trial version and maybe the pro version has some of these features. I think a Wizard would be useful for the intiial creation of the project to collect info such as where your XML file is and where your shared services provider is. Also of note is once the installer finishes, there is no notification and no program group created in your start menu. You can easily find the files in Program Files\BDC Meta Man. This aside, I still think the product is great and I recommend it to everyone.

What is cool is that BDC Meta Man will even allow you to create custom actions, generate methods, and created your IDEnumerator method. What's the catch on the free version? Well you are limited to approximately 180 days of use, can only use SQL Server data sources, can't create associations, and are limited on the number of entities you can use. The trial version is pretty useful by itself though. However, I think the professional version will be worth it to get those features. Anyone that works with the MOSS Business Data Connector, I recommend you go get this product today. The number of hours you will save in development will astound you.

As a new SharePoint developer, you will be directed to install your first Hello World web part in the GAC. Right now red flags should be going off for any non-noob developer. This allows your code to operate with Full Trust (which is bad). To properly deploy a web part, it is necessary to set up Code Access Security. The problem is there are a ton of posts on this subject and there are a lot of different ways to do it. Unfortunately, none of them walk you through it fully or tell you which permissions you need for sure. When configured wrong, CAS has the ability to yellow screen your entire site. This post wll walk you through it and will get you started with a set of permissions. These may not be the best permissions but they are a good starting point from you and they are far from full trust.

In this sample, we will talk about a web part that needs basic SharePoint object model access. Most of this information, I got from reading stuff from Ted Pattison, but I'll explain what issues I ran into along the way. Let's assume we have a web part called MyWebPart and its in an assembly and namespace called MyAssembly. The first thing you have to do is build a feature to deploy your web part. Since this is a complete example, I will explain how to build the feature as well.

  Title="My Web Part Feature"
  Description="This web part does something."
    <ElementManifest Location="elements.xml"/>

The key things to note here are the Feature Id which can be any guid (just make sure its unique within your app) and the reference to Elements.xml. This file defines what the feature is.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MyAssembly Web Parts" 
    <File Url="MyWebPart.webpart" Type="GhostableInLibrary" >
      <Property Name="Group" Value="My Web Part Group">

The main thing of note here is the url to the .webpart file. This is a file that we have to create that tells SharePoint where to load the web part from. You can create this file manually or if somehow you already got your webpart installed in the system (i.e.: threw it in the GAC - don't leave it there), you can export it from the Web Part gallery. Here is a simple .webpart file.

<?xml version="1.0" encoding="utf-8"?>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
      <type name="MyAssembly.MyWebPart, MyAssembly, Version=, 
Culture=neutral, PublicKeyToken=de279125eb887b23">
      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
      <property name="Title" 
      <property name="Description" 
type="string">This web part does something.</property>

As you can see it relatively simple to create so if you can't export it basically you just need the assembly path. One thing to note is that there is a public key token. So be sure and create an snk to sign your project. This is all you need to create a feature. Usually the web part files will go in a folder such as TEMPLATE\FEATURES\\DWP. Although it doesn't really matter. This path corresponds to the directory structure of WSS.

Technically you could copy this out into your SharePoint FEATURES folder and install it. Of course it won't run because of CAS. So how do we request the necessary permissions? We create a solution package. This package alters the default wss_minimal policy for your particular DLL with the permissions it needs. Start by modifying AssemblyInfo.cs.

You will first need references to Microsoft.SharePoint.Security and System.Security.Permissions. Next add the following line. It allows for the assembly to run outside of Full Trust.

[assembly: System.Security.AllowPartiallyTrustedCallers()]

The last thing I recommend in your AssemblyInfo.cs is a static file version during development. The reason for this is that you have to specify the version number on your Safe Control statement later.

[assembly: AssemblyVersion("")]

Next create a Solution folder in your project. In this folder, create a file called Manifest.xml. This file is big so I'll break it down into the key elements.

The root element Solution takes a SolutionId which is another GUID (make it different than your feature one). Next you tell it where to find the Feature.xml file for your web part and then you tell it the path to the actual .webpart file itself. You will need a TemplateFile element for each .webpart you have.

<Solution SolutionId="{5E5FD337-1E3E-47fe-B4BB-A08D70E74352}"

    <FeatureManifest Location="MyProject\feature.xml">


As you probably know any assembly you call in SharePoint needs to be registered as safe. Instead of manually putting it in the web.config, you can add an entry to this file and it will be merged into the Web.config for you. Just specify the name of your assembly and the full path including public key token.

    <Assembly DeploymentTarget="WebApplication" Location="MyAssembly.dll">
        <SafeControl Assembly="MyAssembly, Version=, Culture=neutral, 
	PublicKeyToken=de279125eb887b23" Namespace="MyAssembly" TypeName="*" Safe="True">

Now the fun part. Specifying Code Access Security. You will need to add a reference to Microsoft.SharePoint.Security to your project. Let me first show you the section and then I will attempt to explain why each one is required.

      <PermissionSet class="NamedPermissionSet" version="1" 
	Description="Permission set for MyAssembly.">
        <IPermission class="AspNetHostingPermission" version="1" 
        <IPermission class="SecurityPermission" version="1" 
	  Flags="Execution, ControlPrincipal, ControlAppDomain,
        <IPermission class="Microsoft.SharePoint.Security.SharePointPermission, 
	  Microsoft.SharePoint.Security, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
          version="1" ObjectModel="True" Impersonate="True" UnsafeSaveOnGet="True" 
        <IPermission class="System.Security.Permissions.EnvironmentPermission, 
          mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
          version="1" Read="UserName">
        <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, 
          Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" 
          Read="$AppDir$" Write="$AppDir$" Append="$AppDir$" PathDiscovery="$AppDir$">
        <Assembly Name="MyAssembly" >

Here are the permissions I am using and have gotten to work. I believe some are still too much and as I tweak things I will update this post.

  • AspNetHostingPermission - This permission I believe is used to do any kind of basic ASP.NET function such as add controls to the page.
  • SecurityPermission - I picked this permission up from the working example I had. Mainly it specifies that the code is allowed to execute.
  • Microsoft.SharePoint.Security.SharePointPermission - This sets SharePoint security. Common parameters are ObjectModel = true which grants access to the ObjectModel. Impersonate is not documented. I only assume it has something to do with Imperonsating the current logged in user. UnsafeSaveOnGet allows access to the WSS database during HTTP-GET requests. Unrestricted = true allows the code to execute any SharePoint action. Certain API calls that modify security may require this .
  • System.Security.Permissions.EnvironmentPermission, mscorlib - This can be used to allow access to various environemnt settings. In this case, the username can be read.
  • System.Security.Permissions.FileIOPermission, mscorlib - You have to explicitly grant access to the file system even if you aren't doing any direct access with it. I got these settings with the use of $AppDir$ from another trust configuration file and it seems to work (previously was using unrestricted which is way bad). Also, if you have code that reads any thing from the file system (i.e.: a feature receiver reading an XML file from its folder), you have to grant access to that path.

If you are curious what the actual process does when you instal it, it makes a backup of your web.config (you'll find them in the same directory) and modifies it with the SafeControl elements you specified. It also changes the WSS_Custom trust element to a new configuration file which basically is named with a guid. This file has your custom permission set in it.

Wow, that was a lot. Does that mean we are done yet? Nope, of course not. Next you need to create a file to tell it what goes in the actualy deployment solution. .wsp files are what contain your actual binaries and web part configuratoin. These are actually just glorified cab files. Therefore you have to tell it what files go in the solution. Here is what a typical file would look like.

.OPTION EXPLICIT     ; Generate errors 
.Set CabinetNameTemplate=MyAssembly.wsp     
.set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory
.Set CompressionType=MSZIP;** All files are compressed in cabinet files
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=Package

; add the manfiest file itself
Solution\manifest.xml manifest.xml

; add the feature xml files
TEMPLATE\FEATURES\\feature.xml \feature.xml
TEMPLATE\FEATURES\\elements.xml \elements.xml

; add the webpart file - normally would be all online seperated by a space

bin\Debug\MyAssembly.dlll MyAssembly.dll

Ok, so admittedly, I am not completely clear on the way some of the paths work. Basically it says get the file from path and put it in the specified destination in the wsp file. I am not realy questioning it, somehow it magically works when you install it.

Now we are getting close. We just need to make a deployment script. Ideally, you are developing directly on the WSS/MOSS server. If that's the case it makes deployment really easy. If not, you need to copy your project to the MOSS server first. To aid deployment, create a batch file. The batch file starts by making the wsp file with makecab. It then removes any exisitng solutions of the same name before adding and deploying the new solution. The operations listed in the batch file should seem pretty obvious.

makecab /f Solution\cab.ddf

@SET STSADM="c:\program files\common files\microsoft shared\
web server extensions\12\bin\stsadm"

%STSADM% -o retractsolution -name MyAssembly.wsp -immediate -allContentUrls
%STSADM% -o execadmsvcjobs
%STSADM% -o deletesolution -name MyAssembly.wsp -override
%STSADM% -o execadmsvcjobs

%STSADM% -o addsolution -filename package\MyAssembly.wsp
%STSADM% -o deploysolution -name MyAssembly.wsp -immediate -allContentUrls -allowCasPolicies
%STSADM% -o execadmsvcjobs

This installs the solution to all site collections (-allContentUrls). It is also possible to install to a particular site by specifying the url. Note, the parameter -allowCasPolicies which allows CAS to be specified when the solution is deployed.

After running your batch file, if all goes well, you will have a new feature installed. Activate it by going to the Features activation page. You will then see the web part, in the web part gallery. Preview it or add it to a page to start using it. If all goes well, you will not get any errors.

This process took me a long time to figure out. I hope this comes in handy for all friends jumping on the MOSS bandwagon (and anyone else).

In order to properly install a web part with Code Access Security, it is necessary to create a deployment solution. My next post will cover these and the proper way to specify code access security for a web part. In the meantime though, I wanted to post on a bug I discovered to hopefully help out some other poor *** like myself. Here is an example of some PermissionSets specified in a solutoin manifest (note: the details of the actual permissions are not important).

<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" 
Flags="Execution, ControlPrincipal,ControlAppDomain, ControlDomainPolicy, ControlEvidence" />
<!-- Chage this you lazy *** - unrestricted is bad -->
Microsoft.SharePoint.Security, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
    version="1" ObjectModel="True" Impersonate="True" UnsafeSaveOnGet="True" Unrestricted="True" />
<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, 
Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" 
Flags="ControlThread, UnmanagedCode, Execution" />

Notice the comment in this example. I added a comment like this becaues I wanted to make note of something. I came in after the holiday weekend, deployed my solution and I ended up getting an error "Unable to set required permissions...". An hour or two later, I finally bothered to check the folder with the trust configuration and I noticed that the last line was missing in the merged configuration file. I went back and removed the comment from my manifest file and all the sudden everything started working. So what this tells me is there is a bug in whatever parses the manifest file. Avoid comments in there and you should be safe.

My next post will cover CAS in great detail and will provide you with what the exact permission requirements are for a typical web part.

Repeaters have always been a necessary evil. They are simply the only practical way to do custom formatting when binding to lists of data. Oddly enough, it turns out that not everyone just wants to display a grid of data. In the past, by using the repeater, you have given up a number of things (sorting, paging, editing, inserting, deleting, ok well everything just about). Well thanks to the ListView control, we finally have the flexibility of the repeater and the automatiion of the GridView. Let's take a look.

When first working with the ListView, it will seem very similar to a repeater at first, but there are some differences. You need to start by creating a LayoutTemplate. This allows you to customize the code that surrounds the repeating data (i.e. replaced the Need for the HeaderTemplate and FooterTemplate). In this LayoutTemplate, you must identify a control that will contain the repeated data. This can be a div or ul or whatever. Just give it a unique name and make sure it has a runat="server" tag. The ListView control has an attribute called ItemContainerID. This is set to the vlaue of the control you identified in the LayoutTemplate. In most examples, I have seen this named DataSection. This tells the LayoutView where the repeated data should be injected.

Once you have this configured you can start using the LayoutView just like a repeater using the Eval statement. Here is a simple example of everything put together.

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString=""     
SelectCommand="SELECT * FROM Blah" />
<asp:ListView ID="ListView1" runat="server" DataSourceID="SqlDataSource1" 
          <div id="DataSection" runat="server" >
        <div>  <%# Eval("MyField1") %>          </div>
        <div>  <%# Eval("MyField2") %>          </div>

So far this is very similar to a repeater. Now what if you want to add paging? Well with a repeater, you know you are going to be writing a TON of code to make that happen. Not any more. Now all you have to use is the new DataPager control. It contains a PagedControlID property (which does not show up in the properties editor for some reason). Simply set this to the ID of your ListView and you will have paging. Out of the box, it comes with a Next/Previous pager was well as a numeric pager. It also has support for TemplateFields so you can customize this to your needs. The code for a DataPager looks like this.

    <asp:DataPager ID="DataPager1" PagedControlID="ListView1" runat="server">
            <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" 
                ShowPreviousPageButton="False" />
            <asp:NumericPagerField />
            <asp:NextPreviousPagerField ButtonType="Button" ShowLastPageButton="True" 
                ShowPreviousPageButton="False" />

That's all it takes to get paging. Which is very cool. However there is even more that the ListView control can do. If you click on the Configure ListView link, you will be presented with some default Layout options. Out of the box, the five that are available are Grid, Tiled, Bulleted List, Flow, Single Row. This gives a lot of layout options to start with. When you select one of these it will autogenerate the markup needed in each template to support viewiing, inserting, and editing. The insert/edit functionality looks very similar to the way a DetailsView or FormView works.

There is a ton this control can do and I have only messed with it a little bit. As I use it more, I will post what I discover.

Filed under: ,