17 January 2008

Access Denied when using SPSite.AllWebs

As I have been working to develop a web part that our organization will use to present roll-up data from multiple sub-sites to a top-level site, I've come across a n interesting tid bit that was frustrating at first.

It is the use of SPSite.AllWebs property. Check this code:

            ArrayList Lists = new ArrayList();
            SPSite CurrentSite = SPContext.Current.Site;
            SPContentTypeId ContentType = new SPContentTypeId(PARENT_CONTENT_TYPE);
            SPWebCollection UserSites = CurrentSite.AllWebs;

            foreach (SPWeb web in UserSites)
            {
                foreach (SPList list in web.Lists)
                {
                    if (list.ContentTypesEnabled)
                    {
                        try
                        {
                            bool ContainsType = false;
                            SPContentTypeCollection Types = list.ContentTypes;
                            foreach (SPContentType Item in Types)
                            {
                                if (Item.Id.IsChildOf(ContentType))
                                {
                                    ContainsType = true;
                                    break;
                                }
                            }
                            if (ContainsType)
                            {
                                Lists.Add(list);
                            }
                        }
                        catch
                        {
                        }
                    }
                }

When running as an administrator, this code doesn't cause a single problem. However, if you swap to a non-administrative user that doesn't have full control of the site, you'll get the lovely access denied page. The problem is that you must have full control of the site to be able to use the AllWebs property.

The fix for this is to use a property of the SPWeb object. See the corrected code below:

            ArrayList Lists = new ArrayList();
            SPSite CurrentSite = SPContext.Current.Site;
            SPContentTypeId ContentType = new SPContentTypeId(PARENT_CONTENT_TYPE);
            SPWebCollection UserSites = CurrentSite.OpenWeb().GetSubwebsForCurrentUser();

            foreach (SPWeb web in UserSites)
            {
                foreach (SPList list in web.Lists)
                {
                    if (list.ContentTypesEnabled)
                    {
                        try
                        {
                            bool ContainsType = false;
                            SPContentTypeCollection Types = list.ContentTypes;
                            foreach (SPContentType Item in Types)
                            {
                                if (Item.Id.IsChildOf(ContentType))
                                {
                                    ContainsType = true;
                                    break;
                                }
                            }
                            if (ContainsType)
                            {
                                Lists.Add(list);
                            }
                        }
                        catch
                        {
                        }
                    }
                }

By using the GetSubwebsForCurrentUser() method, I'm able to get a list of subsites that the user does have access to view. If the user doesn't have access to any subsites, there will not be the access denied error message and you can display a warning to the user.

11 January 2008

VB.NET Translation for Thomas Conté's Code

As a side note, here is the translation of the code to VB.NET from Thomas Conté's post.

        Dim PageFile As SPFile = _ManagedWeb.GetFile("default.aspx")
        Dim WebPartManager As WebPartPages.SPLimitedWebPartManager = PageFile.GetLimitedWebPartManager(Web.UI.WebControls.WebParts.PersonalizationScope.Shared)
        Dim TargetList As SPList = Me.GetList(ListName)
        Dim TargetListWebPart As WebPartPages.ListViewWebPart = New WebPartPages.ListViewWebPart()
        TargetListWebPart.ListName = TargetList.ID.ToString("B").ToUpper()
        TargetListWebPart.ViewGuid = TargetList.DefaultView.ID.ToString("B").ToUpper()
        TargetListWebPart.ChromeType = Web.UI.WebControls.WebParts.PartChromeType.TitleOnly
        WebPartManager.AddWebPart(TargetListWebPart, Zone, Ordinal)
        PageFile.Update()

Programmatically Adding Web Parts to Pages

Today I was working to make modifications to a site that was being provisioned via automation code. The code creates a batch of sites and then uploads various data to the site for review by members of the site. The goal of the automation is to insure a consistent user experience with web parts to containing lists and libararies in the same place. However, I couldn't see exactly where this would be accomplished in the object model of SharePoint, so I pulled up Google and began searching. I came across this post by Thomas Conté that was very helpful.

27 November 2007

First Impression of the Business Data Catalog Definition Editor

One of my recent projects involved deciding where to place the results from a database. The database maintains cataloging information on a collection of works housed in the library on the campus where I work. The collection contains around 40,000 entries. While this isn't necessarily a huge list, I determined that a SharePoint list would probably carry too much overhead. This lead me to the Business Data Catalog, and thus my first project to work with the BDC Definition Editor that ships with the latest MOSS SDK.

After installing the application (located in C:\Program Files\2007 Office System Developer Resources\Tools\BDC Definition Editor), I began the exploration of the tool. I started out by adding a ne LOB system. The interface that appeared allowed me to either specify a database connection or a web service connection. This is where I register my first complaint with the tool. I had to provide a complete connection string. This would have been a good place to include a wizard that would help specify the connection options, etc.

Once I provided the connection string and connected to the database, my experience began to improve significantly. I was able to select tables and views to include as BDC entities just by drag-and-drop operations. The BDCDE also recognized relationships and allowed me to choose to import these as well. Once my entities were defined, I just simply clicked on OK and the BDCDE created the bare essentials for each entity. This included both a SpecificFinder and an IdEnumerator.

At this point, I decided to see what functionality was provided without any modifications. I saved the definition and then jumped into my SSP to import the definition. My test environment wasn't set up correctly, so I had to spend a little time tweaking permissions -- but once this was done I could easily view the profile page for each entity. They were working without an issue. However, since no Finder method was created by default, I couldn't use any of the entities with the BDC web parts.

Jumping back into the BDCDE, I created a finder method and was surprised to find there is no way to copy an established parameter to another method. The only difference between the specific finder method and the finder method was the lack of input parameters, so I would need to recreate the return parameter. With a little creative thought, I decided to open the XML definition in Visual Studio. I located my return parameter and copied the xml nodes and placed them in the Finder method. I then imported the definition back into the BDCDE, saved the definition and tested it using SharePoint. I now had a working finder and could return a BDC list.

The next experiment was to create filters. This is where being able to move a parameter node up or down would be very beneficial. I created my filters and tied them to parameters. However, when I attempted to Execute the Finder instance, I received an error message "Could not find exactly 1 Member with the name 'xxxx' Parameter Name: fieldName. After some experimentation, I discovered that the In parameters must be specified prior to the Return parameter. So, back to Visual Studio with the XML definition. I moved the parameter via copy and paste. After I loaded the definition in the BDCDE, I was able to execute the method and filter the result set.

Overall, I'm very impressed with the functionality included with this tool. It has significantly increased my productivity in creating BDC definitions for use in my SharePoint environment. It sure beats attempting to author the definition by hand! However, there are some functions that are missing or should be included in the next iteration to help this become a powerful tool. This includes the ability to clone a return type, move parameters, specify filters later in the development and auto-versioning the file. If you can't afford to purchase BDC Metaman, this is an excellent alternative!