Skip to main content
Select a theme:
   
RSS Feed

How to: Create a Scope Display Group using the API

Yesterday, I talked about an issue with the SearchBox Web Part resulting from not having the Scope Display Group it used defined on my site collection.  I mentioned, the best way to fix this was to have your feature create the scope display group for you, so here is how its done.  To accomplish this, you need to start by getting a SearchContext object to get access to the Scopes object which is used to manipulate anything related to scopes.  Once you have an instance of the Scopes object the AllDisplayGroups property returns a ScopeDisplayGroupCollection.  It is this collection that you can use to create a scope display group.  Unfortunately, however it does not contain anything to find an existing group other than an integer indexer.  This means your choices for determining if the scope already exists are a for loop or a try/catch around the Create method.  I am not sure why the SharePoint API team decided to make it so difficult to determine if an item exists in a collection.  Here is what the code looks like.

// need the collection to modify scope settings

using (SPSite currentSiteCollection = new SPSite("http://mossserver"))

{

    // get a search context and get access to the scope manager

    SearchContext searchContext = SearchContext.GetContext(currentSiteCollection);

    Scopes scopes = new Scopes(searchContext);

 

    // get all scope display groups

    ScopeDisplayGroupCollection scopeDisplayGroups = scopes.AllDisplayGroups;

 

    // create the scope display group and add scopes to it

    ScopeDisplayGroup scopeDisplayGroup = scopeDisplayGroups.Create("My Scope Group", "Scope Group Description",

        new Uri(currentSiteCollection.Url), true);

    scopeDisplayGroup.Add(scopes.GetSharedScope("My Shared Scope 1"));

    scopeDisplayGroup.Add(scopes.GetSharedScope("My Shared Scope 2"));

 

    // update the scope

    scopeDisplayGroup.Update();

}

When you create the new scope, use the GetSharedScope method of the Scopes object to get the shared scopes that you want to add to the display group.  The true parameter on the Create method indicates if you want to show the group in the Admin UI or not.  Once you have made the changes, be sure and call the Update method.

Help! My SearchBox Web Part won't Submit

I have caused this to happen a number of times and the answer isn't obvious at first, so I thought I would post on it and explain what causes it and how to fix it.  The behavior usually occurs for me when I am deploying a SearchBoxEx web part via a feature using an elements.xml file.  What you will see is the web part gets deployed fine, but when you try to submit a query, it doesn't do anything and you get the following JavaScript error.

'options' is null or not an object.

The reason this happens for me is because in my configuration file I am setting the scope dropdown mode to Show, do not include contextual scopes and I have specified a ScopeDisplayGroupName that does not exist yet in my site collection.  Actually any setting for the scope dropdown that displays a dropdown will cause this behavior when the scope display group does not exist.  To fix it quickly, you can either change the scope dropdown mode or create the scope display group on your site collection.  However, the correct way for me to fix this would be to include something in my feature receiver that creates the scope display group.

 

How to: Uninstall a previous version of the MOSS SDK when Add/Remove Programs has been disabled by Group Policy

Well, if you read, the Microsoft SharePoint Products and Technologies Team Blog, you know that the SharePoint SDK and MOSS SDK have been updated to version 1.3 which includes updates for SP1.  Instead of someone that just posts the fact that this came out, I am posting a useful tip that might affect those in a corporate environment.  As you know, many companies like to lock down desktops of their users using a Group Policy.  I find this funny, when they do it to developers.  They don't want you to be able to install software, yet they will give you a tool such as Visual Studio to write software.  I find it even more amusing when they lock down the Add/Remove Programs applet in the Control Panel.  This accomplishes absolutely nothing.

Anyhow, not to get caught on that aside.  Today, I was locked out of removing the previous version of the SDK, so I decided to circumvent the problem using msiexec.exe and determining the GUID of the installer.  The way I determined the GUID was by looking at a log file it put in my temp folder.  Then I just executed the following command and I was able to remove the SDK and install the new one.

msiexec /x {90120000-1121-0409-0000-0000000FF1CE}

Notice the 31i73 spelling of office in that GUID?  Apparently someone at Microsoft has a sense of humor.  This technique would work if you just installed the WSS SDK as well, but I am afraid I don't have that GUID, so you would have to look it up.

BDC: Could not create profile page for Entity

I have seen quite a few searches for this warning in our stats, so I thought I would address the following message received when importing an application definition into the Business Data Catalog.

Could not create profile page for Entity MyEntity.  The error is: Default action exists for application 'MyInstance', entity 'MyEntity'.  Profile page creation skipped.

When you import your application definition, this kind of looks like an error but in fact is not.  This is just the message MOSS gives when you add a default action to an entity.  I describe how to do that in the linked blog post.  Unfortunately, this appears as an error in the Event Log instead of a warning.  This is unfortunate, because your MOSS administrator will likely ask you about it (my last one did).  The long story short.  Don't worry about this message.  It is perfectly normal to receive it when you have specified a default action.

Speaking at Tulsa SharePoint Developer's User Group

I'll be giving my talk on Searching Business Data with MOSS Enterprise Search at the Tulsa SharePoint Developer's User Group on March 10th.  This is the same presentation I gave at TechFest, but I have updated it some and will likely also cover indexing other sources as well as documents.  It's at OSU-Tulsa NH153 at 6:00 PM.  I'll post more info as it gets closer.

How to: Delete Crawled Properties

This unfortunately is not as simple as it should be.  Out of the box, there is not an option to delete a single crawled property.  I sort of understand the reason behind this, but when developing its very easy to get your crawled properties filled with a bunch of garbage as you are trying things out.  It would be nice to clean that up.  So how do you do it?  Your only option is to make use of a setting on each category of crawled properties (i.e.: SharePoint, Business Data, People), labeled Delete all unmapped crawled properties.  You can get there by clicking the Edit Category link after choosing a Crawled Property.  To make use of this, you would need to remove any mapping of crawled properties you don't want to keep first.  I don't really like this as an option all that much, especially when you are removing things from the SharePoint category.  Unfortunately, this appears to be the only way to do it other than going directly to the database (which obviously is not recommended).

Examining the API, there is nothing in there to delete a single crawled property either.  However, you can also use the API to do the above mentioned step if you want.  Include a reference to Microsoft.SharePoint and Microsoft.Office.Server.Search.Administration.  Here is the code to delete the unmapped properties.

using (SPSite currentSiteCollection = new SPSite("http://mossserver"))

{

    // the schema class actually represents managed and crawled proeperties

    Schema schema = new Schema(SearchContext.GetContext(currentSiteCollection));

 

    // get the sharepoint category

    Category category = schema.AllCategories("SharePoint");

 

    // delete all unmapped properties

    category.DeleteUnmappedProperties();

 

    // after delete an update is required

    category.Update();

}

 

Reset IIS after changing a Content Type

Just a word of warning as I have fallen for this one a time or two and I should know better.  Recently, I had a need to change the name of my site columns which meant of course I had to update my content types that were using it.  After, I did this, I discovered, that my ItemEventReceivers were no longer firing.  It was certainly strange behavior.  Then I remembered, I needed to reset IIS (or cycle the app pool or whatever), to drop the cache on the content type before reactivating the feature with the changes in it.

How to: Hack a Class Library Project into a Web Application Project

Kind of a weird tip here, but I have ran into a situation where I have needed to do this.  Basically, I have had an existing class library (for SharePoint development) and for some reason, I have decided I need to add ASP.NET file types to it (i.e. User Controls, Master Pages, etc.).  A class library doesn't have these file types in the Add Item menu so you need a Web Application Project. When it comes down to it a Web Application Project really is the same thing as a class library, it just has some extra settings.  So when it comes down to it, you would either have to recreate the project and add your existing files back into it or just hack the XML of your .csproj file.  I obviously prefer the latter.

To do this, start by Unloading the Project by right clicking your project and choosing Unload Project.  Next Right click on your unloaded project and choose Edit <PrjectName>.csproj.  Paste the line below in the first ProjectGroup element.  Usually Visual Studio puts it underneath the ProjectGuid element. 

<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

That is the only change that is required.  There are some optional settings that you can put in regarding the configuration of Cassini, but none of them are required to get you going.  You can always configure them in Visual Studio.  Once you have made the change, save the .csproj file and Reload Project.  Once the project is loaded, all of the ASP.NET file types will be present in your Add Item menu.  When you compile, it will still compile everything down to a single DLL and you can deploy it just as if it was a regular class library.

How to: Programmatically Activate a Feature

Today, I am continuing my series of posts on how to do basic tasks in SharePoint.  Sometimes there is a need to activate a feature on a site collection or site using code.  There are various reasons why you want to do this, but in my case, I needed to activate multiple features on multiple sites.  The SPFeatureCollection object keeps track of which features are activated on a given site or site collection (not all features available for activation).  There are a number of ways to access it.  You can use the Features property on a given SPWeb or SPSite object.  You can also make use of the WebFeatures or SiteFeatures from SPContext.Current.  To activate a feature, call the Add method and pass it the GUID of the feature.  Be warned that it will throw an InvalidOperationException if the feature is already activated.  As with all SharePoint collections, the only way to determine if it exists is to use the indexer and see if it throws an exception.  Here is an example of activating a feature at the site level.

using (SPWeb currentSite = SPContext.Current.Web)

{

    currentSite.Features.Add(new Guid("{043C4BDD-9745-441a-A9A7-0BCD9B910319}"));

}

If you don't know the GUID of the feature but you do know the name of the Feature, you have to the FeatureDefinitions collection off of the SPFarm object.  Unfortunately, you have to iterate through the whole collection and look at the title to find the feature you need.  There is an example of it in the SDK.

Deactivating a feature is just as simple (provided you know the GUID).  In this case I am using the WebFeatures properties off of the current context.

SPContext.Current.WebFeatures.Remove(new Guid("{043C4BDD-9745-441a-A9A7-0BCD9B910319}"));

I know some might consider this a simple topic, but my goal is to help newcomers to SharePoint as much as possible.

How to: Determine if a Navigation Node Exists

The other day I was reading Charlie Calvert's excellent post on LINQ and Deferred Execution and stumbled upon a logging feature inside LINQ to SQL.  By setting the Log property to a TextWriter (in this case Console.Out), LINQ to SQL will log the query and what parameters it sent to the database server.  Assign it before you execute your query like this.

MyDataContext.Log = Console.Out;

Obviously you don't have to log to the console, you can log to any text writer.  This provides a great way to log what queries are going to the database and helps eliminate the mystery of what queries have been executing in your application.  Obviously, you can view these queries in the debugger, but once your application is in production, this is a good solution.

LINQ to SQL Logging

The other day I was reading Charlie Calvert's excellent post on LINQ and Deferred Execution and stumbled upon a logging feature inside LINQ to SQL.  By setting the Log property to a TextWriter (in this case Console.Out), LINQ to SQL will log the query and what parameters it sent to the database server.  Assign it before you execute your query like this.

MyDataContext.Log = Console.Out;

Obviously you don't have to log to the console, you can log to any text writer.  This provides a great way to log what queries are going to the database and helps eliminate the mystery of what queries have been executing in your application.  Obviously, you can view these queries in the debugger, but once your application is in production, this is a good solution.

How to: Use the MOSS Enterprise Search KeywordQuery class

I don't think there are enough complete examples on using the KeywordQuery class out there, so I am posting this today to help out.  The example in the SDK is close, but not quite enough.  The KeywordQuery class is used to execute a keyword syntax query against MOSS Enterprise Search.  There is also a similar class that uses WSS search which basically works the same way.  To use the KeywordQuery class start by passing it the path to your SSP in the constructor.  This is a good place to use object initializers as I pointed out last week to set other needed properties.

// create a new KeywordQuery class, set the query and set to RelevantResults.

KeywordQuery myQuery = new KeywordQuery(siteCollection)

{

    QueryText = string.Format("Color:\"{0}\"", "Red"),

    ResultTypes = ResultType.RelevantResults

};

In this case I am doing a keyword query searching on the managed property Color with a value of red.  You must set the ResultTypes property to RelevantResults in order to get search results back.  To execute the query, use the Execute method.  This method returns a ResultTableCollection which in turn contains a ResultTable for each type of Result (i.e.: RelevantResults).  You can then load this into a datatable and do whatever with the data.

// execute the query and load the results into a datatable

ResultTableCollection queryResults = myQuery.Execute();

ResultTable queryResultsTable = queryResults[ResultType.RelevantResults];

DataTable queryDataTable = new DataTable();

queryDataTable.Load(queryResultsTable, LoadOption.OverwriteChanges);

Out of the box, this code will only return the default search properties such as Rank, Title, Author, Size, Path, Description, etc.  If you want to return managed properties, make use of the SelectProperties string collection.

myQuery.SelectProperties.Add("Color");

myQuery.SelectProperties.Add("Size");

myQuery.SelectProperties.Add("Quantity");

Whenever I have worked with this class, I have discovered that adding anything to the SelectProperties collection will cause the default properties to no longer be returned.  For example add the title and path properties back to the results with the following.

myQuery.SelectProperties.Add("Title");

myQuery.SelectProperties.Add("Path");

Lastly, using LINQ to DataSet you can further subquery your results if you needed to (i.e.: with a Quantity > 10).

var queryResulsEnumerable = from queryResult in queryDataTable.AsEnumerable()

                            where queryResult.Field<int>("Quantity") > 10

                            select new

                            {

                                Title = queryResult.Field<string>("Title"),

                                Path = queryResult.Field<string>("Path"),

                                Size = queryResult.Field<string>("Size"),

                                Quantity = queryResult.Field<int>("Quantity")

                            };

Here is the complete code sample.

using (SPSite siteCollection = new SPSite(siteCollectionUrl))

       {

           // create a new KeywordQuery class, set the query and set to RelevantResults.

           KeywordQuery myQuery = new KeywordQuery(siteCollection)

           {

               QueryText = string.Format("Color:\"{0}\"", "Red"),

               ResultTypes = ResultType.RelevantResults

           };

 

           //

           myQuery.SelectProperties.Add("Title");

           myQuery.SelectProperties.Add("Path");

           myQuery.SelectProperties.Add("Color");

           myQuery.SelectProperties.Add("Size");

           myQuery.SelectProperties.Add("Quantity");

 

           // execute the query and load the results into a datatable

           ResultTableCollection queryResults = myQuery.Execute();

           ResultTable queryResultsTable = queryResults[ResultType.RelevantResults];

           DataTable queryDataTable = new DataTable();

           queryDataTable.Load(queryResultsTable, LoadOption.OverwriteChanges);

 

           // query the results into a new anonymous type

           var queryResulsEnumerable = from queryResult in queryDataTable.AsEnumerable()

                                       where queryResult.Field<int>("Quantity") > 10

                                       select new

                                       {

                                           Title = queryResult.Field<string>("Title"),

                                           Path = queryResult.Field<string>("Path"),

                                           Size = queryResult.Field<string>("Size"),

                                           Quantity = queryResult.Field<int>("Quantity")

                                       };

       }

Making DataSets tolerable using LINQ to DataSet

Unfortunately, most of us aren't working in a perfect world, so it is bound to happen that you run into a dataset or two.  Whatever the reason (the developer was lazy, you're maintaining legacy code, someone didn't know any better, or you're just working with the SharePoint API), it would be nice if there was a way to make dealing with datasets easier.  LINQ to DataSet does this by allowing you to perform queries or move the data easily into a domain object.

To work with LINQ to DataSet, an extension method called AsEnumerable() is tacked onto the DataTable class making it queryable by LINQ.  Then it is just a matter of knowing the syntax to get individual columns of data.  Using a generic xtension method called Field, we can get the value of a column with the appropriate type.

In this example, we are going to filter the datatable on the ItemDateTime field and return a new anonymous type.  We are assuming the datatable contains columns IntColumn1, StringColumn1, and ItemDateTime.

var queryResults = from queryResult in myDataTable.AsEnumerable()

                   where queryResult.Field<DateTime>("ItemDateTime") < DateTime.Now

                   select new

                   {

                       intColumn1 = queryResult.Field<int>("IntColumn1"),

                       stringColumn1 = queryResult.Field<string>("StringColun1")

                   };

This really makes it easy to perform subqueries on things presented to you via datasets.  What if you want to get away from that nasty dataset and work with a domain object?  LINQ makes that pretty easy as well.  Just assign the values into a domain object.  Here is a simple domain object (note: that it uses automatic properties).

public class MyDomainObject

{

    public DateTime ItemDateTime

    {

        get;

        set;

    }

 

    public int IntColumn1

    {

        get;

        set;

    }

 

    public string StringColumn1

    {

        get;

        set;

    }

}

Here is how you would assign it to the domain object.

var queryResults2 = from queryResult in myDataTable.AsEnumerable()

                    select new MyDomainObject

                    {

                        ItemDateTime = queryResult.Field<DateTime>("ItemDateTime"),

                        IntColumn1 = queryResult.Field<int>("IntColumn1"),

                        StringColumn1 = queryResult.Field<string>("StringColun1")

                    };

Who knows how efficient this is, but it is quite simple.  If you are curious what type queryResults2 is in this, it is an EnumerableRowCollection<MyDomainObject>.  If you want it as a list, you can use the ToList() method, there is also ToArray() and ToDictionary() if that's what you want.  That's all on LINQ and datasets for today.  Hopefully, this will make your next experience with them better.

How to: Get a SPWeb object given a full URL

I have been using Enterprise Search lately to find sites programatically.  The issue I ran into is that I needed to manipulate data on each site that was returned in the results and the only starting point I had was a fully qualified URL.  At first, this task seems quite complicated because after examing the SPWeb object you will notice there is no constructor.  I was hoping, I could just pass it the URL but alas that does not work.  Really, the only way to create a SPWeb object is to use the SPSite object.  So at first thought, I was like well how do I know what site collection something is in, given only its URL.

After a little digging in the documentation, it turns out when specifying a URL in the constructor of the SPSite object, you do not have to specify the path of the root of the site collection.  You can specify the path to anything in the site collection and it will return an SPSite object representing the site collection you want.  Consider the following example.  I have a document at http://MyServer/MySiteCollection/MySubSite/MyDocumentLibrary/Document1.docx.  In this case the site collection is at http://MyServer/MySiteCollection and the subsite is at http://MyServer/MySiteCollection.  To get the SPWeb object, I start by passing the full URL of the document to the constructor of SPSite.  I then just call OpenWeb with the default constructor to get the SPWeb object I need.

using (SPSite siteCollection = new SPSite("http://MyServer/MySiteCollection/MySubSite/MyDocumentLibrary/Document1.docx"))

{

    SPWeb myWeb = siteCollection2.OpenWeb();

}

It turns out that the functionality of OpenWeb differs greatly depending on what was passed into the constructor of SPSite.  In this case, if you call OpenWeb with no parameters, it will return the SPWeb of the site where the document exists.  For more information take a look at the URL below.

http://msdn2.microsoft.com/en-us/library/ms474633.aspx

Using Object Initalizers yet?

Object Initalizers aren't talked about very much as one of the new features in C# 3.0, but I find them pretty useful from time to time (esepcially when dealing with the SharePoint API).  They are great, when there isn't a constructor that has all of the arguments that you need to initialize the class with in it.  Here is an example of how I used it lately with the KeywordQuery class.

KeywordQuery myQuery = new KeywordQuery(siteCollection)

{

    QueryText = string.Format("Color:\"{0}\"", myColor),

    ResultTypes = ResultType.RelevantResults

};

In this case I wanted to initialize the QueryText and ResultType properties. This makes the snytax much cleaner.  Obviously this feature isn't life changing, but I find it a nice convenience.

WSS 3.0 Tools, Visual Studio Extensions 1.1 is nice but still disappointing

At the ODC (where I should be this week), Microsoft announced version 1.1 of the Visual Studio 2005 extensions for Windows SharePoint Services.  This brings a lot of great new project templates and the ability to edit wsp files.  What is it lacking?  Well unfortunately Visaul Studio 2008 support.  I have already moved most of my SharePoint projects into Visual Studio 2008 and having no support for it is completely frustrating.  We're going to have to wait until June to see that support in version 2.0.

Another, thing that really disappoints me is that it still requires Windows Server and SharePoint to be installed just to install the extensions.  I use Virtual Machines to do development some, but I also do a lot of development from Windows XP and Vista and make use of Remote Debugging.  I understand the complexities of running things over the network, but can I at least install the project templates?  I am willing to deploy the files myself, it doesnt have to be automatic for me.

If you are still using Visual Studio 2005 on a Windows Server, then go ahead and install them because they will be pretty useful.  Here is the link.

 http://www.microsoft.com/downloads/details.aspx?FamilyID=3e1dcccd-1cca-433a-bb4d-97b96bf7ab63&displaylang=en

SPSqlDataSource: This control does not allow connection strings with the following keywords: ‘Integrated Security’, ‘Trusted_Connection’.

I ran into this one again lately when deploying.  We are folowing best practices with our connection strings by using Integrated Security.  We have several controls on the web site making use of SqlDataSource controls.  However, at runtime, we are left when an error like the following.

Microsoft.SharePoint.WebPartPages.DataSourceControlDisabledException: This control does not allow connection strings with the following keywords: ‘Integrated Security’, ‘Trusted_Connection’.

This is because SharePoint puts a tagMapping in the web.config that maps the ASP.NET SqlDataSource to its SPSqlDataSource.  Why it does this, I have no idea?  If I check the SDK on that class, there is not so much as even a description of what the class does (come on Microsoft, you still need to work on this SDK documentation).  A workaround is actually pretty simple, remove the tagMapping.  However, you can't just simply delete the line, you must use the remove element to get rid of it.

 <remove tagType="System.Web.UI.WebControls.SqlDataSource, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

This will allow the control to work, but you also need to register the ASP.NET SqlDataSource as a safe control.  You know the syntax, but here it is in case to make it easy.

<SafeControl Assembly="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Namespace="System.Web.UI.WebControls" TypeName="SqlDataSource" Safe="True" AllowRemoteDesigner="False" />

That is all it takes and everything inside SharePoint still seems to work.  My point of today's post is to put out the following questions.

  1. What is the purpose of this inherited control?
  2. Why on earth would it not support integrated security?
  3. Why does the SDK documentation continue to leave out details about key classes?

If you have the answer to any of those, please leave a comment.

Changing the Maxmium File Size in your Enterprise Search Index

In MOSS Enterprise Search, the maxium size of a file that it will index is 16 MB.  This is probably not an issue for most people, but some organizations do have large PDFs, PowerPoints, etc.  This is becoming a little more common knowledge, but I thought I would post it to make more people aware of it.  To change it, you need to add a DWORD at the following registry key.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office Server\12.0\Search\Global\Gathering Manager

Add a new DWORD value of type Decimal called MaxDownloadSize.  The value should be in megabytes so a value of 50 would represent 50MB.  After you change this, you'll need to restart the Office SharePoint Server Search service and perform a full crawl.  Again, this is becoming more common knowledge, but I thought it was still worth mentioning.

How to: Check for Nulls when using LINQ to XML

I continue to work with LINQ to XML, and I thought this might be worth mentioning (although it is somewhat common sense).  An issue often when working with XML attribute (or elements) is that they might not always exists (i.e.: they are null).  Therefore, you need to check for this.  Specifically, this is an issue when you are assigning attributes into a new anonymous type (although it could occur using a regular type as well).  Consider the following example.  What if MyColumn is not present in some of the Item elements in the XML document?  The code would end up throwing an exception when you tried to enumerate items.

var items = from item in assetTypes.Elements("Item")

                     
select new

                     
{

                         
Name = item.Attribute("Name").Value,

                          

MyColumn = item.Attribute("MyColumn").Value

};

 

 

How do you fix it?  First you use the Any() method of the attribute to see if any of that attribute exist.  Then it is just a matter of using shorthand if/then syntax.  Just replace MyColumn with the code below.

MyColumn = item.Attribute("MyColumn").Any() ? item.Attribute("MyColumn") : null

As you can see it is relatively simple, once you know to use Any() to look for the existance of an attribute. On a related note you can apply the same techique to see if an element exists.

COMException (0x81020037): The file blah.docx has been modified by domain\user.

I was working on an ItemEventReceiver the other day and ran into this lovely unmanaged exception.  Luckily, the fix is easy, because I knew exactly what I had done wrong.  I noticed there wasnt a ton out there on Google about it, so I figured I would post to help out others that might see it.  The error occurred when I was uploading a document based upon a custom content type.  After getting prompted to fill in metadata, the following error occurred.

COMException (0x81020037): The file blah.docx has been modified by domain\user.

I immediately knew what I had done wrong.  I forgot to turn off EventFiring before calling SystemUpdate().  This was in turn causing an Update event to fire which obviously wouldnt work because another Update event was alrady in progress.  If you don't remember how to turn off EventFiring, here is what the code looks like.

DisableEventFiring();
item.SystemUpdate(false);
EnableEventFiring();

Things users complain about with Enterprise Search

MOSS Enterprise Search has proven to be a great tool for allowing users to search Line of Business systems and documents at the same time.  Out of the box, the Search Center provides some great looking search resuts.  However I keep hearing the same two things about it.

SearchStats Web Part does not display accurate results counts

This is becoming a fairly well known issue and I think its most likely by design, but it drives users crazy.  It doesn't seem to be an issue until you get a fairly decent amount of results.  I find that customers use this as a test of accuracy of your search index.  In a case where you are pulling data using the BDC from a LOB system, a lot of times the user knows that there are exaclty X widgets in that system.  So when the results count displays X - 373 items, they start asking questions.

Search Center does not support wildcard search

Pretty much everyone that has worked with Enterprise Search and has a SharePoint blog has complained about this one.  Microsoft decided to use keyword syntax for searching using the SearchBox web part.  Maybe, my post will be the 100th post and will get someone to consider doing something about it.  Normally this is fine but it doesn't support wildcard searching.  You would think you could just inherit from CoreResultsWebPart and change the way it gets the search results, but no someone decided it would be a good idea to mark the SearchResultsHiddenObject as internal.  Therefore, you really can't change the way this web part gets its results.  This means you would have to write your own CoreResultsWebPart from scratch.  This isn't inheritenly dificult because the search API is pretty easy to use and you can easily bind it to a ListView.  Of course by doing this, you lose connectivity to all the other web parts.  So that means you need to write connectivity to the SearchStats, Paging, BestBets, etc., web parts.  We should just be able to inherit from the existing parts right?  Nope, someone decided it would be a great idea to mark all of those classes sealed.  Honestly, why are these classes sealed?  That is rediculous.  So what it comes down to is that you have to rewrite the entire search center.  This is sad because the change required to implement wild card search could be done with about 2 lines of code.  Rewriting the Search Center is the approach Modosoft's Ontolica took.

I have mentioned in the past that Ontolica Wildcard Search is out there.  This product works fairly well at wildcard searching, it's free, but there are limitations in the free version.  The free version is basically a selling tool for their full product.  Ontolica took the approach that I described above and they created a whole new Search Center.  The full product is pretty nice and has nice grouping capability and various other ways to filter the data.  However, what I don't like about it is that it adds an additional layer of abstraction.  It requires you to map your managed properties to its own concept of properties.  Also it doesn't look like I can use the open source Search Facet web parts with it (someone correct me if I am wrong on this).  So in order to provide the same level of functionality, I would have to convince the client to fork over additional money to purcahse an Ontolica license.  That being said, if your client is willing to pay for a license, I do think it's a decent solution to the problem.  It has been a few months since I have looked at Ontolica, so if anything has changed with the product, feel free to let me know so I can take a look.

Things users like...

Ok, well I can't just focus on the things users complain about, because there are a ton of things they like.  They like being able to find all of their information in one place (espeically anything that indexes into other systems).  The Search Facet controls have always been a huge hit as well.  Users love drilling down into their data.

Anyhow, the point of my post today is to hopefully adds some more fuel to the fire and hopefully we can see a change some time in the future.

Beware of readonly files when deploying solutions

Sometimes when I am developing a feature that is deployed via solution, after I have deployed it to the server a few times, sometimes I think it is a good idea to just copy out my feature manually to the features folder.  I've got to get out ofthe habit of doing this, because it leads to nothing good.  If you keep files in source control, more than likely one of them might be marked as readonly.  Later when you deploy your solution file again, it will say the feature installed, but more than likely you will see a message like the following.

Executing solution-deployment-mysolution.wsp-0.  The solution-deployment-mysolution.wsp-0 job completed successfully, but could not be properly cleaned up.  This job may execute again on this server.
Operation completed successfully.

What this should really read is, "Something really screwed up while installing your deployment solution, but we're not going to tell you what it is and just make you think everything went fine."  Ok, admittedly this doesnt always mean something is messed up, but in this case, something definitely was.  I went back to my SharePoint site, and could not find my feature anywhere.  I tried retracting and deploying the solution and still nothing.

This meant is was clearly time to go sifting through logs.  It was then when I saw an excpetion that the solution could not copy a file because it was readonly.  To fix it, I retracted the solution and I also manually ran uninstallfeature using stsadm to make sure the features weren't registered in SharePoint.  I then physically deleted the feature folder in question from the SharePoint features folder and reinstalled the solution.

The lesson to be learned here is don't take shortcuts when developing and deploying features or if you do, make sure nothing is readonly or it will cause problems later.

Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor

This error has been pissing me off for a long time.  I have an environment that works great for remote debugging most of the time.  However, every couple of days when I show up for work, I encounter the following error.

Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor named 'DOMAIN\USER@MACHINENAME'. The Visual Studio Remote Debugger on the target computer cannot connect back to this computer. Authentication failed. Please see Help for assistance.

 If you do a Google search, you will find that this is because the remote computer cannot connect back to the desktop for some reason.  By the error it's obviously an authentication issue.  Most articles point to making sure that the account the remote debugger is on also has access to the client machine.  I am using the same account which is local administrator on both, so that is not the issue. 

So how do I resolve this issue when it occurs?  Reboot the client machine.  It's annoying but it does fix the problem.  I have tried restarting Visual Studio and other things and that simply does not work.  The only thing that works for me is rebooting.  Also note that this occurs for me in both Visual Studio 2005 and 2008

NOTE: This site is migrating to DotNetMafia. For the latest tips on Visual Studio 2008, SharePoint, and MOSS, see Corey's .NET Tip of the Day.