More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  Brad Younge's BlogProfileFriendsBlogMore Tools Explore the Spaces community

Blog

    9/10/2007

    Logging Errors in SharePoint 2007

    Quick post here… I had the need to log to the SharePoint log files found at C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\LOGS and had a hard time finding the 1 line of code to do it, so I thought I'd blog about it really quick. If you have this need, use something like the following line of code:

    catch(Exception myException)
    {
    Microsoft.Office.Server.Diagnostics.PortalLog.LogString("Exception Occurred: {0} || {1}", myException.Message, myException.StackTrace);
    }

    Hope this saves someone out there a bit of time searching for this…

    8/11/2007

    Copying, Exporting and Importing SharePoint Document Libraries

    Well, it's been a while since I blogged about anything, but I've got what is hopefully going to be a good one here. I've got a small SPS 2003 to MOSS 2007 migration project to help with over the next week or two and one aspect of the project is the need to move some documents from an SPS 2003 document library to a MOSS 2007 document library. Generally this would be part of an upgrade process, but our client already has a MOSS 2007 farm deployed and in use and wants to slowly build out additional MOSS 2007 sites and site collections and move the documents from their old SPS 2003 server.

    I know there are a couple of ways to do this such as using the Explorer View and then Copy or Cut from one location and Past to another location, but I wanted a way to do this where I could script it out and do a couple of dry runs over the course of a week or two and then do a final cut-over one evening (the SPS 2003 farm is still in use so documents are being added and changed on a daily basis). So, I've written a command line utility called MOSSadm.exe which does the following:

    • Copies a document library from an SPS 2003 site (via Web Service calls) to a MOSS 2007 document library
    • Exports a document library from an SPS 2003 (via Web Service calls) to a ZIP file
    • Exports a document library from a MOSS 2007 site (via the object model) to a ZIP file
    • Imports a ZIP file to a MOSS 2007 document library (via the object model)

    I've packaged this all up into a simple setup program which can be DOWNLOAD HERE. Once downloaded, run the Setup.exe program to install the application on your MOSS 2007 Farm. When you run the setup, the following is done (I included this because I always like to know what's going on when I run a setup program):

    • The following files are put in the 12 Hive bin directory:
    • The following files are put in the C:\Program Files\Statera\MOSSadm_Setup\WSDL Files directory:
      • DocService.asmx
      • DocServicedisco.aspx
      • DocSerevicewsdl.aspx
      • Document03Service.dll
      • Readme.txt

    If you want to use the web service calls to download documents from an SPS 2003 server, you'll need to go read the Readme.txt file installed in the previous step. This tells you what files to copy out to your SPS 2003 server and where to put them in order to install the web service).

    Now… as long as the 12 Hive bin directory is in your path, just open up a command prompt and type mossadm.exe. You should get the following:

    As the help text in the screenshot above shows you, there are 4 operations you can run (note the * next to the first two… those make use of the SPS 2003 Web Services). If you enter in an operation, such as mossadm –o copydoclib then you'll get additional help text such as:

    You're all set and ready to start using this utility. Here are some sample commands you can now use:

    • mossadm –o copydoclib http://www.mySps2003Svr.com "Some Doc Lib" bsmith mydomain pass@word1 http://www.myMoss2007Svr.com "Documents"
    • mossadm –o exportdoclibfromwebsvc http://www.mySps2003Svr.com "Some Doc Lib" bsmith mydomain pass@word1 MyDocumentLibrary.zip
    • mossadm –o exportdoclib http://www.mySps2003Svr.com "Some Doc Lib MyDocumentLibrary.zip
    • mossadm –o importdoclib MyDocumentLibrary.zip http://www.myMoss2007Svr.com "Documents"

    Additionally, you can specify a new folder within a document library to put everything such as:

    • mossadm –o importdoclib MyDocumentLibrary.zip http://www.myMoss2007Svr.com "Documents/a new folder"
    • mossadm –o importdoclib MyDocumentLibrary.zip http://www.myMoss2007Svr.com "Documents/a new folder/myDocs"

    If you have any problems, please feel free to drop me a note and I'll let you know if I have a solution (please include your email address so I can get back to you easily).

    Couple Notes:

    • This utility copies some document properties, but not all of them. It also doesn't copy past versions and it marks the document at the destination location with your name as the creator.
    • When you run the utility you must be on the MOSS 2007 server and it'll be best if you're logged in as a site administrator of the site you're interacting with.

     

    Download Code: click here

    6/14/2007

    Microsoft eScrum Version 1.0 for Team Foundation Server

    My buddy Wade Wegner just notified me of this tool from Microsoft:

    "eScrum is a Web-based, end-to-end project management tool for Scrum built on the Microsoft Visual Studio Team Foundation Server platform. It provides multiple ways to interact with your Scrum project: eScrum Web-based UI, Team Explorer, and Excel or Project, via Team Foundation Office Integration. In addition, it provides a single place for all Scrum artifacts such as product backlog, sprint backlog, task management, retrospective, and reports with built-in context sensitive help."

    To download, visit http://www.microsoft.com/downloads/details.aspx?familyid=55a4bde6-10a7-4c41-9938-f388c1ed15e9&displaylang=en&tm

    Haven't had a chance to play with it yet, but it definitely looks very valuable!

    I also recently became aware of a Project Server 2007 VSTS Connector. To read more about this tool and/or download the code visit http://www.codeplex.com/pstfsconnector

    6/8/2007

    Localize a Date in SharePoint 2007 or WSS 3.0

    One great thing about SharePoint 2007 is the ease of localizing information. Each user of a SharePoint site can change settings such as their Locale, Time zone, Calendar, etc. by selecting the "My Settings" choice that drops down when the user clicks on the Welcome link in the upper right corner of a site and then proceeds to their Regional Settings. Like this:

    If the user selects a different Locale such as "English (New Zealand)", then they will see dates formatted the way they are use to. For example, May 30th, 2007 is displayed as "30/04/2007". This works great with all of the OOTB SharePoint pages and web parts (as far as I know), but if you develop your own page or web part, you'll need to write a little localization code to have the date display correctly. Here's some example code to convert the date in the example above appropriately for each user of the site:

     

    DateTime dtSomeDate = DateTime.Today;
    if (SPContext.Current.Web.CurrentUser.RegionalSettings != null)
    {
    int iCultureId = Convert.ToInt32(SPContext.Current.Web.CurrentUser.RegionalSettings.LocaleId);
    Label lblLocalizedDate = new Label();
    lblLocalizedDate.Text = dtSomeDate.ToString("d", new System.Globalization.CultureInfo(iCultureId));
    }
    else
    {
    lblLocalizedDate.Text = dtSomeDate.ToString("d");
    }

      If you also want to adjust the time to the person's Time Zone, you'll need to write a little bit more code, but I'll blog about that some other day.

    If Having Problems with Visual Studio Extensions for WSS 3.0

    I just posted a blog about how I re-organized a bunch of code into multiple projects to get past some deployment issues I was having with Visual Studio Extensions for WSS 3.0, but have since found what *may* have been one of my problems over the past couple of weeks (though I'm sure it wasn't my only problem). Turns out that if you have a comment in the <Lists> section of your onet.xml file, sometimes (not always – though – very wired) Visual Studio will throw an exception stating that an "object reference not set to an instance of an object".

    For example, if your onet.xml file looks like this, you *may* get the object reference not set to an instance of an object error:

    <Lists>
    <
    List FeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101" Type="101" Title="Templates" Url="Templates" />
    <!--
    Create a List for the Afe Attachments -->
    <
    List FeatureId="ba2bb031-da03-4b55-9e38-170ae9edc59e" Type="101" Title="AFEAttachments" Url="AfeAttachments" />

    </
    Lists>

     

    If you remove the highlighted comment, it works! Adding comments to other areas of your onet.xml file may also cause issues, but I haven't tested all the different areas to see where it is problematic. If anyone else comes across any other areas, I'd love to get a comment from you.

    Tip if Having Problems with Visual Studio Extensions for Windows SharePoint Services 3.0

    Over the past several weeks, I have been having some serious headaches using the Visual Studio Extensions for WSS 3.0 and it came to a climax today. For a project I'm working on, we have the need to create several content types, several Custom Lists based on these content types, and then a Site Definition that makes use of these content types and lists. I've put a good amount of time into creating a Visual Studio project with several "Project Items" based on the "Visual C# Project Items – SharePoint" templates that get installed with the mentioned extensions, image shown here:

    In summary, I performed the following steps to get where I'm at now:

    1. Create a new VS C# Project based on the "Blank Site Definition" template
    2. Added 4 content type items
    3. Added 9 list definitions (some of which are based on the content types I created)

    One of the great things about the VS Extensions for WSS is that as I wrote the content types, lists and site definition, I could easily deploy everything to my local SharePoint site collection by right-clicking on the VS Project and selecting deploy.

    Things went pretty well for a couple of weeks, but then I started getting the following error when I tried to deploy: "object reference not set to an instance of an object". The error was NOT an error with my code (I know - bold statement), but instead was an exception being thrown by Visual Studio. You see, when you choose to deploy to project, VS actually has to do a good amount of work to re-organize all of the files you've created, create a manifest, create the actual .wsp file (SharePoint Solution File) and then a batch script (setup.bat) which is executed after all is done to actually deploy your SharePoint *stuff* to the server. The exception was occurring during the step within Visual Studio that creates the .wsp and setup.bat files.

    For a few weeks I was able to get past this problem by shutting down and then re-launching Visual Studio and then try deploying again and whola… it would work. Lately, however, the problem started occurring more and more often and has gotten harder and harder to work around. Yesterday, it got so bad that my workaround became:

    1. Check all code into TFS
    2. Shut down Visual Studio
    3. Delete the local copy of my code
    4. Launch Visual Studio and do an Open from Source Control to get the code
    5. Run the Deploy – and it would work… not a single change to any of code or CAML

    Then, if I made even the smallest, most insignificant change to any of the files in this project, the Deploy would fail and I'd have to go through the above steps again.

    Well… today it got even worse. Now, I can't get it to deploy at all!!! Even if I roll back all my changes to a version that was deploying fine this morning, it still doesn't work… Ugh.

    So, over the past 2 hours I've been trying to narrow down the problem by excluding directories in the project (which I had tried before), and narrowed it down to the Site Definition folder. If it's included in the project, the Deploy fails. If it's NOT included in the project, the Deploy works.

    -----------------------------------------------------------------------------------------------
    ----- Took a break here to figure out what's causing the problem ------
    -----------------------------------------------------------------------------------------------

    OK… I'm back, and I've got a "kind-of-ok" solution, so here it is:

    1. Since I had so many Content Type and List definitions in the existing project, I created a new project just for the site definition
    2. Now that there's no Site Definition in my original project, I had to remove all the instances of my lists, or they'd get created at the Site Collection Root, which is not where I want them. So, I went through each of the list directories in my original project and if there was a file by the name of "instance.xml", I excluded it from the project.
    3. In my new project, I updated the default.aspx and onet.xml files with the contents of the default.aspx and onet.xml files in my original project (which are now excluded from the original project, but I kept them on the hard drive just for this reason).
    4. Things are looking good, and both projects are deploying well, but now since the Site Definition and all of the List Definitions are in separate Visual Studio Projects, Visual Studio will no longer do the leg work to make sure that each of the list definitions (which were implemented as Features) are activated in the Web when it gets created, nor will it do the work that creates the instances of the lists which I want instances of. So, I needed to add 2 lines to the onet.xml file in my Site Definition project for each List Definition I wanted to have activated and an instance created for. Those lines are shown below – highlighted.

    <Configurations
        <Configuration ID="1" Name="Blank"
            <Modules
                <Module Name="DefaultBlank"/> 
                <Module Name="UploadTempate"/> 
            </Modules
            <SiteFeatures
                <!-- BasicWebParts Feature --> 
                <Feature ID="00BFEA71-1C5E-4A24-B310-BA51C3EB7A57"/> 
                <!-- Three-state Workflow Feature --> 
                <Feature ID="FDE5D850-671E-4143-950A-87B473922DC7"/> 
            </SiteFeatures
            <WebFeatures
                <Feature ID="00BFEA71-4EA5-48D4-A4AD-7EA5C011ABE5"/> 
                <!-- TeamCollab Feature --> 
                <Feature ID="F41CC668-37E5-4743-B4A8-74D1DB3FD8A4"/> 
               <!-- MobilityRedirect --> 
                <Feature ID="ba2bb031-da03-4b55-9e38-170ae9edc59e"/> 
                <!-- AFE Attachmetns--> 
            </WebFeatures
            <Lists
                <List FeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101" Type="101" Title="Templates" Url="Templates" /> 
                <List FeatureId="ba2bb031-da03-4b55-9e38-170ae9edc59e" Type="101" Title="AFEAttachments" Url="AfeAttachments" /> 
            </Lists> 
        </Configuration>
    </Configurations>            

     

    In the code above, there are two things to note:

    1. The Feature Id can be found near the top of the schema.xml file for the list in a comment line (which Visual Studio uses to create the wsp package):
      <!-- _filecategory="ListDefinition" _filetype="Schema" _filename="schema.xml" _uniqueid="ba2bb031-da03-4b55-9e38-170ae9edc59e" -->
    2. Be sure that you move the <Lists> section to be BELOW the <SiteFeatures> and <WebFeatures> section since the feature needs to be activated in the web before a list can be created based on the feature.

    Now that I have my code re-organized in Visual Studio as stated above, deployment is very smooth. However, it has only been one day since I re-organized as stated above, so only time will tell if this is more stable than my original organization. If this turns out to be troublesome after a few days or weeks, I'll be sure to blog about what I find then.

    6/4/2007

    Profile Linked Content Query in MOSS 2007

    Earlier this year, I had the opportunity to create a MOSS 2007 web part for a client which integrates information from the current user's profile with a MOSS Search Query in order to find information most relevant to the current user (similar to the Personalized Content Objects in CMS). For example, the client has offices all over the world, and one of the things they wanted to do was have a "Most recent content additions" page which would show the most recent information added to the portal, filtered by the region that the information was targeted to (e.g. North America, Europe, Asia, Middle East, Africa, etc.). I accomplished this through the implementation of a web part that could be placed on any page and configured by the content manager for the site. The web part turned out pretty well, and the other day a couple guys I've worked with over the years from MCS, Henry Winkler and Matt Fangman, asked if I'd be up for blogging about it, so that brings me here.

    The Problem

    At first glance, we thought we'd use audiences, which seems to be the obvious choice, to target the content as described above, but when we dove a bit deeper into the customer's requirements, it was apparent that as soon as we started adding additional filter criteria such as current user job role and primary technology of interest, that the number of audiences which would be needed increased exponentially, making correct categorization of content extremely difficult (because when you post content, you need to choose each and every audience that the content should be targeted to). Let me give you an example:

    Let's say that we wanted to create a page which would be called "Recent Postings in your Region related to your Job". This page would display content with the following criteria:

    • Region = current user's region (North America, Europe, Asia)
    • Job Role = current user's job role (Project Manager, Engineer, Tester, Accounting)
    • Primary Technology = current user's primary technology (MOSS, Microsoft Dynamics, BizTalk, Portfolio Server)

    With just the example choices given above, if we wanted to use audiences to target this information, we would need to create 48 audiences (3*4*4) to cover all of the possible combinations. If we added just one more region, such as Africa, then the number or audiences would increase to 64 (4*4*4). In case this isn't entirely obvious, here are the first few audiences we would need:

    • North America Project Managers for MOSS
    • North America Project Managers for Dynamics
    • North America Project Managers for BizTalk
    • North America Project Managers for Portfolio Server
    • North America Engineer for MOSS

    Taking this one step further, if we had 12 regions, 8 job roles and 15 technologies, we'd need 1440 audiences, not going to happen.

    The Solution

    So, we needed to come up with a different solution. Without much discussion, it was obvious that the best thing to do was going to be looking up information from current user's profile and combining this with a MOSS SQL Search query to find the relevant information. Luckily for us, there was a parallel Microsoft Identity Integration Server project (see http://www.microsoft.com/miis) being implemented which would provide all of the profile information we were going to need, so we didn't have to worry about populating this information within the scope of our project.

    Now that we had a good approach, it was merely a matter of implementing the web part and providing the content manager a way to "match up" content metadata with the user's profile attributes. This was achieved relatively easily with the use of the MOSS Enterprise Search SQL Syntax (see http://msdn2.microsoft.com/en-us/library/ms493660.aspx) query capabilities and a little bit of code to structure the query appropriately before sending it off to the search server.

    So, for the example above, we can get the region, job role and primary technology out of the user's profile (e.g. Asia, Engineer & MOSS), inject the values into the search query, execute the search and then display the results.

    The resulting web part turned out very well and has been successfully deployed to a number of sites and site collections. Since initial deployment, the web part has been extended so that the content manager can further filter on things such as content type, search scope or keywords.

    The Result

    If you plan to read further through this blog, you'll probably want to download the Visual Studio Solution I've made available so you can follow my code examples (there's too much code to blog about everything, so I'm just going to focus on the things pertinent to this post). To download a Zip file of the Visual Studio 2005 Solution, go to my Office Live Site Download Page and click on the QueryForIt.img link (2 IMPORTANT NOTES: 1. I had to rename the file to have a *.img extension in order to get it to work, so after you download the QueryForIt.img file, rename it to QueryForIt.zip before opening. 2. When you build the solution, you must switch your build configuration to RELEASE or the post-build commands will fail – or you can go edit the DDF file).

    The code is spread across several files, but this is primarily due to the fact that I used some existing custom toolparts plus one just for this web part as well as a couple of helper classes to do some dirty work. Here's a breakdown of the code:

    • Visual Studio Project (Statera.Moss.Webparts.QueryForIt)
      • Source (directory)
        • LookupListAndChoiceHelpers (directory with helper classes)
        • Toolparts (directory with copies of the existing custom toolparts I created)
        • FilterChooserToolpart.cs (new custom toolpart created for this web part)
        • PersonalizedQueryHelper.cs (very small class used to bind to the SPGridView for display purposes)
        • QueryForIt.cs (the main code for the web part)
      • manifest.xml – Manifest file for deployment (see very end of post)
      • Statera.Moss.Webparts.QueryForIt.ddf – input file for the makecab utility in order to create a SharePoint solution (see very end of post)

    The Custom Toolparts

    In order to make configuration of the web part as easy as possible, I create a few custom toolparts to be used when modifying the web part. These custom toolparts' functionality include:

    1. Ability to select which Content Types should be searched
    2. Ability to select which Search Scope to be searched
    3. Ability to further filter results with an additional Query Phrase (keyword search)
    4. Ability to select which columns are displayed in the search results
    5. The column which the results should initially be sorted by and which direction
    6. An advanced query phrase (this is where you can link in information from the user profile)

    Here are some screenshots of what the toolparts look like:

    The third image above titled "Filter & Customize Output" is where the user can input a keyword, select the columns to display, and finally enter an "Advanced Query". The "Advanced Query" textbox is where the content manager would write the code which will be injected into the SQL Syntax Search Query. The syntax for this "Advanced Query" textbox is very specific and is explained in the comment below the textbox. (I know… this isn't very user friendly, but I wasn't expecting the typical content administrator to be modifying this web part. If you were expecting this, then this custom toolpart could be extended to be much more user friendly).

    Integrating User Profile Information with the Enterprise SQL Search

    Once the toolparts were created, the next step was to implement the actual web part which would integrate the current user's profile information with a basic SQL Query. I started with a base web part with properties to hold the information provided by the toolparts above and then wrote the code to create and then execute the query. Here are a few code snippets of how the query is created and executed (if you want to make fun of my variable naming techniques, go ahead… habits are hard to break):

    private PersonalizedQueryHelper GetPersonalizedQueryHelperClass()
    {
    DataSet mySearchQueryResultsDS = new DataSet("MyMetaDataDS");
    try

        //----------------------------------------------------------- 
        // Execute the query 
        //----------------------------------------------------------- 
        FullTextSqlQuery kRequest = new FullTextSqlQuery(ServerContext.Current); 
        kRequest.QueryText = SQLQueryToExecute
        kRequest.TrimDuplicates = false; 
        kRequest.ResultTypes |= ResultType.RelevantResults; 
        kRequest.RowLimit = _iResultLimit; 
        ResultTableCollection resultTbls = kRequest.Execute(); 
        if ((int)ResultType.RelevantResults != 0) 
        { 
            ResultTable tblResult = resultTbls[ResultType.RelevantResults]; 
            if (tblResult.TotalRows == 0) 
            { 
                // NO RESULTS 
                PersonalizedQueryHelper myPersonalizedQueryHelperEmpty = new PersonalizedQueryHelper(); 
                return myPersonalizedQueryHelperEmpty; 
            } 
            else 
            { 
                // Get the results, put them in a Datatable and create an instance of MetadataBrowserHelper 
                DataTable relResultsTbl = new DataTable(); 
                relResultsTbl.TableName = "TheSearchResults"
                mySearchQueryResultsDS.Tables.Add(relResultsTbl); 
                mySearchQueryResultsDS.Load(tblResult, LoadOption.OverwriteChanges, relResultsTbl); 
            } 
        }
    }
    catch (Exception myExc)

        _lblQueryResult.Text = String.Format("The following error occurred while trying to execute the search query for this web part. If the problem continues, please contact your administrator: {0}", myExc.Message);
    }

    }

    public string SQLQueryToExecute

        get 
        { 
            … 
            //----------------------------------------------------------- 
            // Create the Personal Query filter if applicable for the WHERE CLAUSE 
            //----------------------------------------------------------- 
            string sPersonalizedWhereClause = ""
            if (_sPersonalFilters != null) 
            { 
                foreach (string sFilter in _sPersonalFilters) 
                { 
                    if (sFilter.IndexOf(':') > 0) 
                    { 
                        string sMetaDataField = sFilter.Split(':')[0]; 
                        string sProfileField = sFilter.Split(':')[1]; 
                        string sProfileFieldValue = GetCurrentUserProfileProperty(sProfileField); 
                        if (sProfileFieldValue.Length > 0) 
                        { 
                            if (sPersonalizedWhereClause.Length == 0) 
                                sPersonalizedWhereClause += string.Format(" {0} = '{1}' ", sMetaDataField, sProfileFieldValue); 
                            else 
                                sPersonalizedWhereClause += string.Format(" AND {0} = '{1}' ", sMetaDataField, sProfileFieldValue); 
                        } 
                    } 
                } 
            } 
            string sKeywordsWhereClause = ""
            if (_sKeywords.Length > 0) 
            { 
                sKeywordsWhereClause += string.Format(" CONTAINS('\"{0}\"') ", _sKeywords); 
            }
    … 
        }
    }

    private string GetCurrentUserProfileProperty(string sKey)

        try 
        { 
            UserProfileManager profileManager = new UserProfileManager(ServerContext.Current); 
            UserProfile myCurrentUserProfile = profileManager.GetUserProfile(SPContext.Current.Web.CurrentUser.LoginName); 
            return myCurrentUserProfile[sKey].Value.ToString(); 
        } 
        catch 
        { 
            return string.Empty; 
        }
    }

     

    The first box of code above contains the code used to actually execute the Enterprise Search using the SQL syntax. The second box of code above contains the code which is used to generate the part of the SQL where clause containing information from the user's profile. And the third box of code shows the few lines it takes to actually retrieve the info from the current user's profile.

    Formatting of Search Results

    If you've gotten this far through this post, then you're here for the long haul, so I should point out that the results returned from the SQL Search are not quite ready for being displayed. When result content contains either site columns of type "Option" or "Lookup", the results returned from search are not always textual, but sometimes are integers (representing the ID of the actual choice that was made). Furthermore, if the possible values are Multi-select, then the results returned from the Search Query are delimitated with ';#'. In order to convert these results to more friendly strings, I had to write the FixLookupAndMultiChoiceColumns() method which exists in the Util.cs file (not shown in this post). If you look at this method, you'll notice that I look through all of the results returned from the search and correct the columns that need correcting. Something to note: when I first wrote this method I was a little concerned with performance, but once I got to testing it, it appeared to be just fine.

    Binding the Search Results to an SPGridView

    Now that I have the results, I wanted to display them in a grid which had both sorting and paging functionality. I've written a lot of .NET code over the years and this use to be kind of a pain in the butt task that we all had to do. Luckily, though, with the implementation of the GridView class in .NET 2.0, and the extension of this component by WSS 3.0 to the SPGridView class, I'm able to do this without writing any paging or sorting code. However, this isn't so straight forward when you have to write all of the code within a web part and don't have use of code-in-front, but I'm not going to get into it in detail here, but you can read my posting titled My Memberships Web Part for WSS 3.0 to get a bit more information about how I accomplished this with the use of an the SPGridView and an ObjectDataSource.

    Deploying the Web Part with a SharePoint Solution

    In addition to writing the code for the web part, I also setup the Visual Studio solution so that a SharePoint solution (.wsp) file would be created for easy deployment to SharePoint whenever you run a build. This was accomplished by doing the following:

    1. Create a Manifest file and place it in the Visual Studio Project (manifest.xml)
    2. Create a DDF file for the makecab utility to use and place it in the Visual Studio Project (Statera.Moss.Webparts.QueryForIt.ddf)
    3. Add the following two lines to the post build event command line (Build Event tab of the project properties) so that the WSP file specified in the DDF file gets created with every build:
      cd $(ProjectDir)
      makecab /f Statera.Moss.Webparts.QueryForIt.ddf

    Now, every time I run a build, the Statera.Moss.Webparts.QueryForIt.wsp file gets created.

    To add and deploy this solution to a SharePoint 2007 web, you need to follow these steps:

    1. Copy the wsp file (provided in the ZIP file I posted) to the SharePoint server and then execute the following STSADM commands from the command line:
      stsadm –o addsolution –filename Statera.Moss.Webparts.QueryForIt.wsp
      stsadm –o deploysolution –name Statera.Moss.Webparts.QueryForIt.wsp –immediate –allcontenturls \
      NOTE: for some reason, if you copy and paste the above two lines into a command prompt, it won't work. You need to type them out by hand.
      NOTE: when you deploy this solution, the you have to deploy it to a specific Web App, and the DLLs go into the web app's bin directory instead of the GAC – long story there… still trying to figure out the problem… something to do with my Namespace is too deep - I think)
    2. Add the web part to the list of web parts for the Site Collection
    3. Add the web part to a page
    4. Modify the web part and setup the properties. Most of these properties are self explanatory except for the "Advanced Query" textbox, but I've provided some instructions below it (as mentioned before).
      Note: in order to use a metadata property within the Advanced Query (e.g. "region" in the screenshots above), the metadata property must be added to the list of "Metadata Property Mappings" in the configuration of the Search Server for the SSP (go to "Shared Service Administration à Search Settings à Metadata property mappings" and add any and all properties you want to be able to query by to the list of properties).

    Bonus: From Date & To Date Connected Web Parts

    In addition to the functionality already mentioned, I also added the ability to connect up to two Date Filter web parts. You would do this if you wanted to limit the results to say, postings or modifications over the past 7 days. To do this, you would add two date filter web parts to the page, set their default values accordingly, and then connect them to the "FromDate" and "ToDate" properties on this web part.

    Conclusion

    Well… that pretty much covers it. Here's a screenshot of the final web part in use with the results sorted by the created date descending. Note that since I'm using the SPGridView, I didn't have to write any sort of formatting code within my web part, it all gets taken care of so the grid matches other grids within SharePoint (and if the CSS files are customized, those customizations will be applied to this web part as well).

    If you download the code and make any cool additions to it, I'd love to get a copy of what you did. Just send me an email at:

    Check out Wade Wegner's blog for great information on Commerce Server 2007.

    6/3/2007

    Got my own Office Live Basics site now…

    Something I've been meaning to check out for a while now and finally had some time this morning is the new Office Live services. Turns out, there are three services that you can sign-up for which provide you with public facing and private MOSS 2007 sites:

    • Office Live Basics    - Free
    • Office Live Essentials    - $19.95/month
    • Office Live Premium    - $39.95/month

    I went ahead and got my own Office Live Basics site and have just started checking it out. There are a lot of great things that a developer like myself can have fun doing with this site…. And it'll be good knowledge to have and be able to talk to with customers. I'll probably blog more about this in the future, but here are a couple of URLs that may be of interest to you:

    6/2/2007

    My Memberships Web Part for WSS 3.0 (SharePoint 2007)

    The other day I wrote a quick web part for our corporate intranet which shows a list of all sites that the current user is a member of. The part is a bit slow, because it traverses the site hierarchy and checks to see if the current user is a member of the "Members" group of each site, but it works (for the most part). I just came across a blog posting titled "Increased performance for MOSS apps using the PortalSiteMapProvider" at http://blogs.msdn.com/ecm/archive/2007/05/23/increased-performance-for-moss-apps-using-the-portalsitemapprovider.aspx that I'll probably try to use to improve the performance of this web part at some point, but don't have time right now. I'm doing this blog post in order to share a few techniques I used to create the web part, and also give a link to a zip file of the web part Visual Studio project I created (yep… I'm finally giving back to the SharePoint community after taking-taking-taking).

    I've zipped and uploaded the Visual Studio solution to my Office Live Site site download page, and the file is named Statera.Moss.Webparts.MyMemberships.img (NOTE: after you download the file, you need to change the extension to .ZIP and then you will be able to unzip it).

    There's not very much code to this project at all, but there are a few things of interest above and beyond the purpose of the web part, so I'll walk through a few things here.

    Overview

    1. All of the code is in the two c# files within the Source Directory
    2. The "MyMemberships.cs" file is the code for the web part
    3. The "MyMembershipsHelper.cs" file is the code for the MyMembershipsHelper class which is used as an ObjectDataSource within the web part so that I can make use of all the great features that the SPGridView server component.
    4. Here's what the part looks like:

       

    Making good use of the SPGridView

    The SPGridView is a great SharePoint class which inherits from the .NET 2.0 GridView class and adds in all of the necessary css classes and other style stuff so that the grid looks like the other grids in SharePoint. I found a good posting about using the SPGridView at http://blogs.msdn.com/powlo/archive/2007/02/25/displaying-custom-data-through-sharepoint-lists-using-spgridview-and-spmenufield.aspx. The great thing about using the SPGridView (or even just the .NET 2.0 GridView class) is that you don't have to write any paging or sorting code if you get it all hooked up correctly.

    Hooking the SPGridView up correctly within a web part is a bit tricky since you don't have use of the designer in Visual Studio. If you look at the code you'll notice a couple of things. First, the MyMembershipHelper class is extremely small… so small that I'll post its entire contents here:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Data;
    using System.Collections;
    using System.Collections.Specialized;
    using Microsoft.SharePoint;

    namespace Statera.Moss.Webparts

        public class MyMembershipsHelper 
        { 
            DataTable _myData; 

            public MyMembershipsHelper() 
            { 
                _myData = new DataTable(); 
            }

            public MyMembershipsHelper(DataTable myData) 
            { 
                _myData = myData; 
            }  

            public DataView GetQueryResults() 
            { 
                DataView myView = new DataView(_myData, "", "sitename", DataViewRowState.CurrentRows); 
                return myView; 
            } 
        }
    }

     

    All this class does is provide a place to store a DataTable as well as a method that the SPGridView can call when it needs the data (GetQueryResults). Now if you look at the MyMemberships cless, you'll see that during the initialization of the Web Part that an ObjectDataSource object is created and added to the web part's controls:

    //-----------------------------
    // Create the ObjectDataSource object
    _myObjectDataSource = new ObjectDataSource("Statera.Moss.Webparts.MyMembershipsHelper", "GetQueryResults");
    _myObjectDataSource.ID =
    "_myObjectDataSource";
    _myObjectDataSource.ObjectCreating += new ObjectDataSourceObjectEventHandler(_myObjectDataSource_ObjectCreating);
    this.Controls.Add(_myObjectDataSource); 

    By doing this, you can then set the DataSourceID of the SPGridView object to this new ObjectDataSource and the SPGridView will know where to get its data:

    _grdResults = new SPGridView();
    _grdResults.ID = "_grdResults";
    _grdResults.EnableViewState = false;
    _grdResults.DataSourceID = "_myObjectDataSource";

    The only other thing you need to do is create the _myObjectDataSource_ObjectCreating method so that you can set the object instance of the ObjectDataSource to the instance of the myMembershipsQueryHelper class that you'll later create and populate:

    public void _myObjectDataSource_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
    {
        
    e.ObjectInstance = _myMembershipsQueryHelper;
     

    Now that we have the SPGridView and ObjectDataSource objects all linked up, we just need to create an instance of the myMemberships class populate it with the sites that the current user is a member of. 

     

    Determining Site Membership

    The only other interesting methods are the code is the GetMyMembershipsHelperClass() and the AddSite() methods. The GetMyMembershipsHelperClass() method creates the DataTable in order to store the membership information and then calls the AddSite() method. The AddSite() method is a recursive method (calls itself) in order to traverse the site hierarchy. Probably the most important line of code in the AddSite() method is:

    string sIdOfMemberGroup = mySpWeb.Properties["vti_associatemembergroup"];

    This line of code gets the ID of the "Members Group" so that we can later determine if the current user is a member of this group. The ID of the "Members Group" is stored in the Properties bag of the SPWeb when the site is created and a Members Group is created. NOTE: you can set the value of this property programmatically if you ever have the need to change it or set it to be a different group than the one that SharePoint sets up for you.

    Now that we have the Group ID of the "Members Group" for the site, we can determine if the current user is in this group with the following code:

    SPGroup myMemberGroup = mySpWeb.Groups.GetByID(iGroupId);
    if (myMemberGroup.ContainsCurrentUser)

        string sSiteUrl = mySpWeb.Url; 
        string sSiteDescription = mySpWeb.Description; 
        DataRow myNewRow = mySites.NewRow(); 
        myNewRow["sitename"] = "<li>" + sSiteName + " </li>"
        myNewRow["siteurl"] = sSiteUrl; 
        myNewRow["description"] = sSiteDescription; 
        mySites.Rows.Add(myNewRow); 
        mySites.AcceptChanges();
    }

    I had to put this code inside a try/catch block in order to catch the Access Denied exceptions that occur if the current user doesn't have enough permission to enumerate the Members Group. This is one place where performance could be drastically improved.

     

    Deploying the SharePoint Solution

    So, in addition to writing the code for the web part, I also setup the Visual Studio solution so that a SharePoint solution (.wsp) file would be created for easy deployment to SharePoint whenever you run a build. This was accomplished by doing the following:

    1. Create a manifest.xml file
    2. Create a DDF file for the makecab utility (Statêra.Moss.Webparts.MyMemberships.ddf)
    3. Putting the following two lines in the post build event command line (Build Even tab of the project properties):
      cd $(ProjectDir)
      makecab /f Statera.Moss.Webparts.MyMemberships.ddf

    Now, every time I run a build the Statêra.Moss.Webparts.MyMemberships.wsp file gets created.

    To add and deploy this solution to a WSS 3.0 or SharePoint 2007 web, you just copy the wsp file to the SharePoint server and then execute the following STSADM commands from the command line:

        stsadm –o addsolution –filename Statera.Moss.Webparts.MyMemberships.wsp
        stsadm –o deploysolution –name Statera.Moss.Webparts.MyMemberships.wsp –immediate –allcontenturls

    NOTE: for some reason, if you copy and paste the above two lines into a command prompt, it won't work. You need to type them out by hand.

    Well… that's all I've got to say at this time. Feel free to let me know if you have any problems…

    Other key words or phrases:  Membership Web Part MOSS 2007 WSS 3.0 Windows SharePoint Services 3.0

    Blogging to Spaces with Photos

    Well… it's been about 9 months since I blogged last… not that anyone has noticed, and I thought I'd try to get back into it. The first thing I wanted to do was figure out how to Blog from Word 2007 and include pictures. Well… it took me about an hour and $25, but now its working great. Here's proof:

    I didn't do a tremendous amount of research, so there may be cheaper/free ways of doing it, but here's what I did…

     

    • Connected my Word Blogger to my spaces account. Note, when it asks for your "Space Name", enter just the name part of your space, not the entire URL. The Secret word is something that I was able to setup by going to spaces.live.com and editing my account

    • Clicking Picture Options gave me the following screen, and the help links weren't of much help, though they did point me to Photobucket. After going to www.photobucket.com and creating an account, I found that in order to upload images via FTP, I had to subscribe to the Pro service. It was only $25 for a year, so instead of spending my time looking for a free service, I went ahead and upgraded to pro. Once I did this, and read the FTP guide Photobucket provided, I was able to get the Upload and Source URLs figured out and entered in (NOTE: the xxxxxxx in the upload URL is where your password would go).

    • That was it!

    So, now I'll click on the button in the upper left hand corner of Word and the images in this Blog Post will be FTP'd to Photobucket and the content should go right up to my Spaces blog. Let's give it a try…

    8/23/2006

    Problems with SharePoint 2007 Beta 2 Configuration Wizard

    I just spent a good part of my day trying to get the SharePoint 2007 Beta2 Configuration Wizard  to work and finally got past a very frustrating problem.  I continued to receive "Exception: System.InvalidOperationException: This access control list is not in canonical form and therefore cannot be modified" even after reading several blog entries and following the suggestions.  The most thorough entry I found was on Nerd Notes.net at http://www.nerdnotes.net/blog/PermaLink.aspx?guid=93c5d391-39aa-4905-be2e-c12233a9d929, but even after following the suggestions there, I still continued to get the error.
     
    After looking through the log file that the configuration wizard produces I found that the last registry key that was attmpted to be accessed was at HKLM/SOFTWARE/Microsoft/Office Server.  I browsed to this key using RegEdit.exe, right clicked on it and selected Properties.  Just like in the Nerd Notes blow, I received a dialog stating that the Key's permissions were corrupt.  After clicking OK and then closing out of RegEdit, I re-ran the Configuration Wizard and it completed successfully!
     
    I you are having similar problems but can't find the exact resolution, my recommendation would be to search through the log file that the configuration wizard produces for the last reference to a registry key.  Then use RegEdit to pull up the permissions on the key, and if you're lucky, you'll get an error message stating that the permissions are corrupt and you'll be able to click OK to resolve them.