in

Dot Net Mafia

Group site for Tulsa area .NETdevelopers, with blogs dealing with (usually) .NET, SharePoint, and other Microsoft products, as well as some discussion of general programming related concepts.

Corey's .NET Tip of the Day

Bringing you the latest time saving tips for SharePoint, MOSS 2007, ASP.NET, LINQ, and Visual Studio 2008
  • Solution Deployment blocked by CommVault

    I tend to run into this issue every time I do a production deployment on this one particular MOSS farm.  What happens is when I deploy a solution, stsadm of course says everything worked great but I try to look for my new code and its not there.  Eventually, I remember to look at the solutions page in central admin where I will always find an error.  It turns out on a few of the frontend servers, the solution deployment failed because it can't update one of the DLLs in the bin directory.  It's the typical, this file can not be copied because it is in use error message.  After going through the list of processes, I finally found the one that was the culprit SP2K3DocBackup.exe.  After a little research, I discovered this belonged to CommVault Galaxy (a data backup application).  The solution was simple, once I killed this process on each server, I redeployed my solution file and it installed correctly.  I am not sure why this process locks your DLLs.  You would think it would use Volume Shadow Copy or something so it wouldn't have to.  Hopefully this will help if you run into this situation.

  • Select Where In with LINQ

    I can't talk about SharePoint all the time, so I thought I would talk about how to perform a type of query with LINQ.  In T-SQL you might have wrote something like this at one point.

    SELECT Title, Id FROM Table1 WHERE Id IN (SELECT Id FROM Table2)

    Basically, I am looking for all rows in Table1 where there is a matching Id in Table2.  Effectively I want a contains or exists type comparison between tables or lists.  I recently ran into a scenario where I wanted to do this and the syntax wasn't immediately obvious to me so I thought I would post something on it.  Let's start by defining a simple class.

    public class MyClass

    {

        public string Title

        {

            get;

            set;

        }

     

        public int Id

        {

            get;

            set;

        }

    }

    We'll then start by adding some test data to a list of this class.

    List<MyClass> myClassList = new List<MyClass>();

    myClassList.Add(new MyClass() { Title = "Title 1", Id = 1 });

    myClassList.Add(new MyClass() { Title = "Title 2", Id = 2 });

    myClassList.Add(new MyClass() { Title = "Title 14", Id = 14 });

    In this case I want to compare it to a list of integers to find which items of MyClass match.

    List<int> subQueryList = new List<int> { 1, 14, 97, 3, 11 };

    Now, to perform the LINQ query.  The key to this kind of query makes use of the contains extension method on the list.

    var filteredList = from myClass in myClassList

                        where subQueryList.Contains(myClass.Id)

                        select myClass;

    Enumerating this query would return MyClass objects with an Id of 1 and 14.  This works well given a simple list of integers but what if we have two different lists of MyClass?  Here is our second list.

    List<MyClass> myClassList2 = new List<MyClass>();

    myClassList2.Add(new MyClass() { Title = "Title 5", Id = 5 });

    myClassList2.Add(new MyClass() { Title = "Title 2", Id = 2 });

    myClassList2.Add(new MyClass() { Title = "Title 14", Id = 14 });

    One way that comes to mind to handle this is to write a LINQ query to get a list of the Ids for the second list and then query in a similar manner.

    // get a list of ids from the other list of classes

    var idList = from myClass in myClassList2

                select myClass.Id;

     

    // subquery using the idList

    var filteredList2 = from myClass in myClassList

                       where idList.Contains(myClass.Id)

                       select myClass;

    Enumerating filteredList2 would return MyClass objects with an Id of 2 and 14.  Instead of using a subquery to get a list of Ids, what about something like this?

    var filteredList3 = from myClass in myClassList

                        where myClassList2.Contains(myClass.Id)

                        select myClass;

    This will compile just fine, but as expected it returns no results.  Although the myClass in each list with Ids of 2 and 14 have identical values, they are different objects.  If you wanted to exert some additional effort, you could get this to work, but I am not going to cover that today.

    Posted Jul 01 2008, 05:14 PM by CoreyRoth with 2 comment(s)
    Filed under:
  • Changing the maximum file upload size in SharePoint

    The default file upload size in SharePoint is 50 MB.  Sometimes, you may have users that need to upload documents that are larger.  This is quite easy to change.  Simply go to Central Administration -> Web Application General Settings.  Select the Web Application you want to change, and you will see a textbox to specify the maximum upload size.  Before I remembered to look here, I tried changing it the old ASP.NET way, by changing the following line in the web.config.

    <httpRuntime maxRequestLength="51200" />

    Interestingly, the file size specified here is also 50 MB, but changing it here does not allow you to upload a larger file into a document library.  I also figured I might have to change it to match whatever I had set in SharePoint but you don't.  You can leave it the same and upload a 100 MB file into SharePoint just fine (provided you changed the setting).  So from what I can tell SharePoint does not use this setting at all and it would only apply if you had written a custom upload form using ASP.NET.

  • Viewing Property Bag Values with SharePoint Designer

    I really don't use SharePoint Designer all that much but on occasion I do find it really useful.  The case I am talking about today is using it to view and modify values in a site's property bag.  To do this, open SharePoint Designer and go to the Open Site menu.  When looking at the Open Site menu it isn't apparent at all that you can enter a URL there, but you can.  Enter the full path (i.e.: http://moss-server/MySiteCollection/MySite) to the site you want to look at then click Open.  Enter your credentials if necessary and then you should get a view of the site.  Ok, that part is pretty basic and you probably already knew how to do that.

    To view the property bag.  Go to Site -> Site Settings.  Then click on the Parameters tab (not sure why they didn't just call it Property Bag).  On this tab, you can see the values of all of your custom property bag values (it doesn't show anything built-in).  If you need to, you can also modify and remove values from the property bag here.   Hopefully this tip is useful sometime in the future when you need to check the values of your property bag.  It sure beats the alternative of writing code to display it or firing up the debugger.

  • Wildcard Search with MOSS People Search

    After having success with implementing wildcard search with MOSS, I decided to look into this whether or not I could inherit from the PeopleCoreResultsWebPart today to implement wildcard search functionality and unfortunately I did not get very far.  The issue is that the class is sealed.  This means that I can't just simply inherit from it and change the method that sets the query.  I now understand that the only reason CoreResultsWebPart is not marked sealed (its the only search web part that isn't) is because it had to be left unsealed so that the People Search web part could inherit from it.  I can probably do some more hacking by creating my own web part and using some reflection to load the PeopleCoreResultsWebPart and change what I need to, but that is going to take some time.  In the meantime, you can always use the WildcardSearchCoreResultsWebPart (but of course you give up all of the fancy PeopleCoreResultsWebPart functionality) or you can make use of Ramon Scott's handy JavaScript.

  • Running out of disk space? Check WSS_AdminService.log

    I repeatedly find that WSS_AdminService.log is reguarly a source of problem on the SharePoint servers that I work with.  The problem is that this file gets written to every minute and it keeps growing and growing and there doesn't appear to be any known way to tell SharePoint to truncate it or purge it like normal logs.  I examined the contents and there appears to be no valuable information in it compared to the regular logs in the 12 hive.  At one point, this log file on one of my servers had grown to be over 1 GB.  This can be a real issue on a virtual machine you use for development since you don't always have a lot of disk space available.  The file can be typically found in the location below.

    C:\Documents and Settings\Default User\Local Settings\Temp\WSS_AdminService.log

    You may want to rename it, move it, or do something with it, but I usually just delete it.  It would be nice if SharePoint would take care of this file for you, but for now it looks like you are going to just have to take care of it manually or write a script to delete it periodically.

  • Content Index is Corrupt

    I came in after the weekend to find out that someone reported the following error when trying to use my Enterprise Search page.

    The search request was unable to connect to the Search Service.

    As you may know, this is a very generic error that means something with Enterprise Search is really messed up.  Either that or the service isn't running.  I decided to check the Event Log and was pleasantly surprised to actually find something in it.  Here is what I found.

    Query machine 'MOSS_SERVER' has been taken out of rotation due to this error: The content index is corrupt.   0xc0041800.  It will be retried in 15 seconds. Component: b1df2e81-4375-4110-b5b1-ddf3acb128bc.

    Why my content index got corrupt, I don't know.  In this particular case it was a development machine running on a virtual machine.  Maybe there was an issue writing to the disk at some point, who knows.  After doing some searching on Google, about the only solution I saw found was to remove the Office SharePoint Server Search service and recreate it.  I didn't particularly like this solution, so I ended up making use of the Reset all crawled content link.  Not exactly an ideal situation, but that is what I did and then I recrawled all of my content.  Everything seems to be working great so far.  Hopefully it will not happen again.

  • How to: Create a Custom Document Library

    Not too long ago, I talked about how to remove the Explorer View from a document library.  As part of the post, I mentioned that I would post how to build a custom document library in the near future.  That is what I am covering today.  I really don't know what the best practice is on how to create a document library, I am just going to show you how I have done it in the past.

    Usually, the way I start is by taking a copy of the existing builtin SharePoint DocumentLibrary feature and add it to a new Visual Studio solution.  Typically I would put this in a 12 hive folder (i.e. TEMPLATE\FEATURES).  The next step is I rename the DocumentLibrary feature folder (i.e. CustomDocumentLibrary).  The next thing you will want to do is edit the Feature.xml file.  We have to make some basic changes so that this is considered a new feature.  Therefore, you will want to pick a new GUID for the Id and give it a new Title and Description.  You may also want to change Hidden to False so that you can activate it and deactivate it through the UI.

    Next, we need to edit the ListTemplates/DocumentLibrary.xml file.  This file defines the list template itself.  The first thing you need to change is the Type.  The Type number for a Document Library is 101.  It is recommended that user defined list templates start with 10000, so pick any number in that range that isn't used.

    The next file you need to modify is the DocLib/Schema.xml file.  On the root List element, you will want to change at the minimum the Title element.  You may also want to set EnableContentTypes to true if you want to use custom content types.  Set FolderCreation to false if you have a custom folder type and set VersioningEnabled to true if you want versioning enabled in the document library.  The Url property I believe is the default URL that you document library will use when created.  It is specified as a relative path (usually just the folder name).   Here is what a typical root list element looks like.

    <List xmlns:ows="Microsoft SharePoint" Title="My Custom Document Library" Direction="$Resources:Direction;" Url="My Custom Documents" BaseType="1" xmlns="http://schemas.microsoft.com/sharepoint/" EnableContentTypes="True"  FolderCreation="false" VersioningEnabled="true">

     

    I have been leaving the BaseType attribute set to 1 and things have been working fine.  However, I have a feeling that it should probably be set to 101 so that Document Library is the base type.  In the MetaData section, you can override the folder and document content types.  I discussed how to properly do that in this post.  You just need to reference the content types you are using and then also add Field elements for each custom site column you are using in those content types.  Again, the post mentioned above describes how to do that.   The Scema.xml file is also where you can remove (or create) different views for your document library.  This is where you would go to remove the explorer view that I mentioned a while back.

    Once you have made these changes, you are ready for deployment.  There are other files in the DocLib folder, but I typically don't mess with them.  Your custom document library can be deployed by copying out the feature and using stsadm or you can create a solution file for it.  Once the feature is activated you are ready to create instances of your new document library.  Also note that the Visual Studio Extensions for SharePoint can also create a lot of these files for you, but you will still need to go through and customize your various XML files.

  • NEW! Web Part for Wildcard Search in Enterprise Search

    I have had countless people tell me that they want to be able to do wildcard search in MOSS Enterprise Search using the existing Search Center site templates.  The Search Center site template uses keyword query syntax which does not support wildcards.  To get wildcards you need to use a full text SQL query.  Until now the only solutions to getting wildcard search was either use Ontolica's Wildcard Search or write your own search page.  Neither option has ever been appealing to me.  Ontolica replaces the entire search center and provides an extra layer of abstraction to your managed properties.  I didn't want to write my own search page because the search center already produces great looking results and it would be a lot of effort to reinvent everything on the search page.  The reason why you would have had to write your own search page is that you could not get access via conventional means to the objects to change the query.

    This is why I finally decided to take matters into my own hand.  I really just wanted to just inherit from CoreResultsWebPart, change out the keyword query with a FullTextSqlQuery and call it good.  Anyone who may have looked at this before knows it is not that simple.  The class that does all the work SearchResultsHiddenObject is marked internal.   Right now this seems an impossible task unless you bend the rules a little.  That's right.  I decided I am going to break OO rules and use reflection to get to the properties I needed.  Some people say you should never do this and that its a hack.  I agree to some extent, but when you are programming against an API and the provider of said API doesn't give you the tools you need to do your job, sometimes you have to bend the rules.  Let's face it.  Microsoft should have given us this support out of the box and at the minimum should have allowed us to change the query that the CoreResultsWebPart executes through the API.  They did neither so here we are.

    So how does the code work?  Well, CoreResultsWebPart happens to be the one class in all of the Enterprise Search controls that isn't marked sealed.  That is good news.  Through the use of Reflector, I discovered the method I need to inherit from is SetPropertiesOnHiddenObject.  Microsoft was even nice enough to mark this method as virtual for me.  I then got access to the type and then used the base type to get a FieldInfo object for the private field srho (which is the SearchResultsHiddenObject). 

    // get the type of the current object

    Type coreResultsWebPartType = this.GetType();

     

    // get the private field containing the searchResultsHiddenObject

    FieldInfo searchResultsHiddenObjectField = coreResultsWebPartType.BaseType.GetField("srho", BindingFlags.NonPublic | BindingFlags.Instance);

    Once, I got access to the hidden object, I read the value of the KeywordQuery property to get what the user searched for.  I then had to set this value to null, because I was replacing the keyword query with a FullTextSqlQuery. 

    // get the actual internal srho object attached to CoreResultsWebPart

    object searchResultsHiddenObject = searchResultsHiddenObjectField.GetValue(this);

     

    // get the type of the srho

    Type searchResultsHiddenObjecType = searchResultsHiddenObject.GetType();

     

    // get the keyword query property

    PropertyInfo keywordQueryProperty = searchResultsHiddenObjecType.GetProperty("KeywordQuery", BindingFlags.Instance | BindingFlags.Public);

     

    // read what the user searched for

    string keywordQuery = (string)keywordQueryProperty.GetValue(searchResultsHiddenObject, null);

     

    // set the keywordProperty to null so we can change it to a fullTextQuery

    keywordQueryProperty.SetValue(searchResultsHiddenObject, null, null);

    It was then just a matter of forming a new SQL query string (check the code on how I did that), and setting some additional fields _IsFullTextQuerySetFromForm and m_bIsKeywordQuery

     

    // get the fullTextQuery field

    PropertyInfo fullTextQueryProperty = searchResultsHiddenObjecType.GetProperty("FullTextQuery", BindingFlags.Instance | BindingFlags.Public);

     

    // create a new query and set it

    string fullTextQueryString = GetFullTextQuery(keywordQuery, keywordsAsQuery);

    fullTextQueryProperty.SetValue(searchResultsHiddenObject, fullTextQueryString, null);

     

    // this field needs to be set to true to use a full text query

    FieldInfo fullTextQuerySetField = searchResultsHiddenObjecType.GetField("_IsFullTextQuerySetFromForm", BindingFlags.NonPublic | BindingFlags.Instance);

    fullTextQuerySetField.SetValue(searchResultsHiddenObject, true);

     

    // tell the srho that it is not a keyword query any more

    FieldInfo isKeywordQueryField = searchResultsHiddenObjecType.GetField("m_bIsKeywordQuery", BindingFlags.NonPublic | BindingFlags.Instance);

    isKeywordQueryField.SetValue(searchResultsHiddenObject, false);

    The code for this is really pretty simple (aside from the reflection).  Had the SearchResultsHiddenObject been marked public, we could have had this functionality over a year ago, but oh well. 

    Installation

    Installation is relatively simple and instructions are included in a readme file in the document.  A solution package has been provided for ease of installation.  Once the package has been installed activate the Wildcard Search Web Part feature on your site collection.  I went with a site collection feature because the search center site does not have a web part gallery in it.  Now that the feature is activated, go to your results page in your Search Center, edit the page, and add the Wildcard Search Core Results Web Part to the Bottom Zone.  You can then remove the old CoreResultsWebPart.  Also note that this web part requires .NET Framework 3.5 because I used LINQ to XML to parse through the SelectColumns property.

    Usage

    Once you have the web part installed, you can perform a wildcard search by just adding an asterisk to whatever you type in the search box.  For example app* would return mataches on app, apple, and application.  You can also set the Always Use Wildcard property in the Miscellaneous property settings to always perform a wildcard search.

    One thing to note.  Wildcard searches reek havoc on your search relevance.  Where you might be used to having nice clean looking results with your keyword searches, your wildcard searches will look different.  It might not always be obvious why a particular item was returned in the search results.  The best thing to do is try and see if it works for your particular situation.

    This type of web part probably could have been sold, but I thought it was more important to give it to the community.  This feature gets asked for by MOSS customers all the time.  Finally there is an easy solution to implementing it.  Since this web part is new, I am sure there are going to be issues with it.  Please, log any issues you run into in installation or in us on the issue tracker of the CodePlex site.  Currently, it only supports simple keyword queries.  I still need to implement support for passing scopes and managed properties, so look for that in an update soon.

    You can find the release files at CodePlex.

    Corey Roth is a MOSS consultant for Stonebridge.

  • WSS 3.0 Tools, Visual Studio Extensions 1.2 running under Windows Vista

    Back in February when version 1.1 of the Visual Studio Extensions for SharePoint came out, I really complained about the lack of support for Visual Studio 2008 and Windows XP / Vista.  Version 1.2 finally came out so I decided to look and see if operating systems other than Windows Server 2003 are supported and I was surprised to see Windows XP and Windows Vista on the list.  I immediately downloaded it, tried the installer but saw that there was still a requirement to have WSS installed.  Luckily, in the last week or so the guys at Bamboo, posted a utility to allow WSS to be installed on Windows Vista.  I decided to give the utility a try and it worked flawlessly.  I then tried installing the extensions again and everything worked.  The Visual Studio Extensions are nothing all that exciting, but they can be useful from time to time.  I am just glad I was able to get them to install. 

    I did notice that Windows Server 2008 was missing from the list so no telling if it will work there or not.

    WSS 3.0 Tools, Visual Studio Extensions 1.2

  • How to: Remove the Explorer View from a Document Library Template

    If you are building a custom document library solution, there may be a time where you don't want users to use the Explorer View of the document library.  I am talking about the view itself and not the Open with Windows Explorer action in this case.  The correct way to do is of course is to create your own custom document library template (I'll be covering that in a future post) and then modify the schema.xml file.  If you want to go crazy and unsupported, you can also modify the schema.xml of the built-in DocumentLibrary feature and accomplish the same result for the out-out-the-box document library.  Removing it is just a matter of removing the appropriate View element in the schema.xml file.  In this case, you are looking for an element similar to the one below.

    <View BaseViewID="3" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,Explorer_View;" Url="Forms/WebFldr.aspx" SetupPath="pages\webfldr.aspx" RequiresClientIntegration="TRUE" ReadOnly="TRUE">

    It will have a BaseViewId of 3 and a Display Name indicating that it is the explorer view.  Just delete this entire View element and reactivate the feature and this view will not be present on any new document libraries you create.  Again, I recommend creating your own document library template instead of modifying the built-in one.  I'll cover this soon, since I haven't found too many good sets of instructions out there on how to do it.

  • Implementing Metadata Inheritance using an ItemEventReceiver

    When implementing an ECM solution, it is often necessary to come up with a way for documents to inherit metadata from a parent folder.  The main reason of doing this is so that user's can search on any of the document's properties.  Other ECM solutions do this for you (yes there are other ECM solutions other than SharePoint), but WSS requires a bit of code to make it happen.  The way you do it is by creating an ItemEventReceiver and then by setting properties in the event handling method of various events.  To start out, I am assuming you have created your own content types for a custom folder and document.  We'll call these Custom Folder Type and Custom Document Type respectively.  I am also assuming you are deploying these via feature.  The reason is you need to set up an Item Event Receiver on the Custom Document Type and it is much easier to configure via feature.

    The first thing we need to do is create the class to contain the event handling methods of the ItemAdded, ItemUpdated, and ItemCheckedIn events (there are more event types, but these are the only ones we need).  I recommend putting this class in its own library (or in a library with other item event handling methods).  My reason for this is that I have only been able to get an item event handling method to fire is when the assembly is in the GAC.  If you have read my blog in the past, you know I almost always recommend against this, but in this case, I don't know of any choice.  To implement the class, start by having your custom class inherit from SPItemEventReceiver.   You can then override various event handling methods.  In this case, I want to enforce the metadata inheritance whenever a document is added, updated, or checked in.  To do this, we override the ItemAdded, ItemUpdated, and ItemCheckedIn methods.  The contents of all the methods is typically the same.  They call a method to do the inheritance and if it fails, we attempt to cancel the event (although I have never had it cancel anything when there is an exception).

    public override void ItemAdded(SPItemEventProperties properties)

    {

        base.ItemAdded(properties);   

     

        try

        {

            InheritCustomProperties(properties.ListItem);

        }

        catch (Exception e)

        {

            properties.Cancel = true;

            properties.ErrorMessage = "Error inheriting metadata.";

            properties.Status = SPEventReceiverStatus.CancelWithError;

        }

    }

    The SPItemEventProperties allows you to get access to the list item using the ListItem property.  We then pass it to our custom method which does the inheritance.  In this example, I am assuming that the parent folder has two properties: Product Id and Color, that we want to copy into the newly added (or updated) item.

    protected void InheritCustomProperties(SPListItem childItem)

    {

        // make sure that the parentFolder and its item are not null

        if ((childItem.File.ParentFolder != null) && (childItem.File.ParentFolder.Item != null))

        {

            SPItem parentItem = childItem.File.ParentFolder.Item;

     

            // copy properties from parent to child

            childItem["Product Id"] = parentItem["Product Id"];

            childItem["Color"] = parentItem["Color"];

     

            // event firing must be disabled otherwise this update will cause another event to fire

            DisableEventFiring();

            childItem.SystemUpdate(false);

            EnableEventFiring();

        }

    }

    One thing to make sure is that a parent item exists for you to inherit from (obviously you would need to do something different if you were uploading documents to the root of a library).  Next it is just a matter of getting a reference to the parent item and then start copying each property.  Before updating the child item, you need to make a call to the base class's DisableEventFiring method to keep it from firing an event when you save the item.  Next, you should call SystemUpdate instead of Update which will save the item but not save it as a new version. 

    That is all the code that is involved.  However, you still need to modify the XML of your Content Type feature to tell it to use your even receiver.  In your content type's Elements.xml file, you'll need to add some new entries to the XmlDocuments element (add one if you don't have one already).  Then, add a section similar to the one below to your file.  You will see one receiver for each event type (ItemAdded, ItemUpdated, ItemCheckedIn).

    <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/events">

      <spe:Receivers xmlns:spe="http://schemas.microsoft.com/sharepoint/events">

        <Receiver>

          <Name>Custom Document Added Event Handler</Name>

          <Type>ItemAdded</Type>

          <SequenceNumber>10001</SequenceNumber>

          <Assembly>CustomEventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2d79125eb887b9e</Assembly>

          <Class>CustomEventReceivers.CustomDocumentLibraryItemEventReceiver</Class>

          <Data />

          <Filter />

        </Receiver>

        <Receiver>

          <Name>Custom Document Updated Event Handler</Name>

          <Type>ItemUpdated</Type>

          <SequenceNumber>10001</SequenceNumber>

          <Assembly>CustomEventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2d79125eb887b9e</Assembly>

          <Class>CustomEventReceivers.CustomDocumentLibraryItemEventReceiver</Class>

          <Data />

          <Filter />

        </Receiver>

        <Receiver>

          <Name>Custom Document Check In Event Handler</Name>

          <Type>ItemCheckedIn</Type>

          <SequenceNumber>10001</SequenceNumber>

          <Assembly>CustomEventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2d79125eb887b9e</Assembly>

          <Class>CustomEventReceivers.CustomDocumentLibraryItemEventReceiver</Class>

          <Data />

          <Filter />

        </Receiver>

      </spe:Receivers>

    </XmlDocument>

    The SequenceNumber is the order in which your event handling method fires.  Microsoft typically recommends something starting with 10,000 and above to avoid conflicts with their event handlers.  The Assembly element, takes a standard assembly reference to a DLL in the GAC.  The Class element specifies the namespace and class which contains the event handling methods.

    Again, I recommend keeping the feature for your content type and your event receivers in different assemblies.  Install your assemblies first with a solution package.  Then you can install the feature containing your updated content type.  Once you have it installed, add some metadata to a parent item, save it, and then try uploading a new document to verify that the metadata was copied to the new document.  If the event handler threw an exception, most likely it did not display a visible error.  Unfortunately, this means you'll have to do some GAC debugging.  It sounds like quite a bit of work, but it really isn't bad.

  • Error: The parameter is incorrect. (Access denied by BDC.)

    When attempting to configure the Business Data Catalog for use with MOSS Enterprise Search, it can be quite common to get this message when you try crawling for the first time.  The cause is actually pretty simple, but I figured I would give some troubleshooting advice today.  When you get this error, the fact is, your Enterprise Search crawl account cannot access some portion of the item in the BDC.  When crawling, permission has to be given to the crawl account in three different places: the BDC itself, the Application Definition you are crawling, and the individual entities inside the Application Definition.

    When troubleshooting this error, the first thing you need to do is identify your crawl account.  The easiest way to do this is to go to the Search Settings page on your SSP and see what the default content access account is.  When looking for the source of this error, you can try looking in the Event Log, but typically the only time you see an error in there is if you are indexing a SQL Server data source and the crawl account doesn't have access to the SQL Server database.  This is a good time to check and make sure that the crawl account does have access to the database.  Your next best bet is to check the latest log file in your 12 hive's LOG folder.  Search for something like "Access Denied" and see what you find.  Here is an example of what I found.

    Closing chunk on Exception: Access Denied for User 'MOSSTEST\MOSS_Search'. Securable MethodInstancewith Name 'GetGetContactIdsResultsInstance' has ACL that contains:      User 'MOSSTEST\administrator' with Rights 'Execute, Edit, SetPermissions, UseInBusinessDataInLists, SelectableInClients' 

    In this case, my error is indicating that my crawl account did not have access to the entity itself.  Let's assume though that we don't know what permission is needed.  First, start off by going to the Business Data Catalog Permissions link on your SSP.  Make sure your crawl account is listed and that it has Execute and SelectableInClients permissions.  You can try and click the Copy all permissions to descendants link and hope it copies the same permissions down to the application definition and entity itself, but more than likely it won't work.  So, the next step is to go to the Application Definition, and make sure your crawl account has the same permissions there.  Again you can try the Copy all permissions to descendants link, but I recommend also going to each entity and verifying that your crawl account has permission. 

    At this point, try crawling again and hopefully everything will work.  To alleviate some of the pain in the process, you can also set your permissions via Application Definition.  One thing to note is that I have tried just granting Domain Users (or some other group) the needed permission to crawl, but it does not seem to work for some reason.  It seems that you must explicitly grant access to the crawl account (and not some group that it is a member of) for the crawl to succeed.  I have set up numerous BDC Crawls and it seems like I still run into this error on a regular basis.  Hopefully, with these steps, you can get your crawl to run.  If you can't get it to work, try checking your log files.

  • How to: Write a value into the property bag

    I have ran into a few people that didn't even know what the property bag is on the SPWeb object, so I figured that would be a good topic for a post.  The property bag exposed by the Properties and AllProperties collections on the SPWeb object gives the developer a way to cache information on a site.  It can be compared to ASP.NET's cache object but the values are shared across the farm and will persist through server reboots.  Unfortunately, using the property bag isn't as simple as you would think.  In fact to get it working in all situations, it requires a number of settings to allow it to work.  It's probably enough to scare a lot of developers away from it.

    Before we start, let me talk about the differences between Properties and AllProperties.  Properties is a collection of type SPPropertyBag.  It is sort of considered deprecated.  You can use it to add new items to the property bag, but it will only return a subset of everything in the property bag when retrieving data.  Why is that?  I have no idea.  So instead, Microsoft later implemented the AllProperties Hashtable which contains everything in the property bag.

    Here is what the start of our code block looks like to write to the property bag.  This assumes it is inside a using block to get access to an SPWeb.

    // unsafe updates are required to be able to write to the property bag

    currentWeb.AllowUnsafeUpdates = true;

     

    // you must check to see if the collection has a value in the assigned key already

    if (!currentWeb.AllProperties.ContainsKey(key))

        currentWeb.Properties.Add(key, myValue);

    else

        currentWeb.AllProperties[key] = myValue;

     

    // update the properties

    currentWeb.Properties.Update();

    currentWeb.AllowUnsafeUpdates = false;

    There are a number of things to note here.  First, it requires AllowUnsafeUpdates to be set to true.  Secondly notice that the AllProperties object actually has a method to check to see if a key exists.  This is probably the only collection in SharePoint that lets you check to see if something exists without throwing an exception.  This is of course because, the type is Hashtable and not a custom SharePoint collection.  If the key does not already exists, you add it to the Properties object (not AllProperties).  If a value already exists, you store the value using an indexer with the AllProperties object.  You might be able to get this to work other ways, but after lots of attempts, this is what works for me.  After the value is stored, you have to call Update on the Properties object if you want it to actually store the values.  If you have multiple values to store, you only need to call this once.  Lastly, be sure to remember and turn off AllowUnsafeUpdates.

    The above code works great...as long as the account running it is an administrator.  If you need this code to work when executed by a regular user, there is an additional step.  You have to use SPSecurity.RunWithElevatedPrivileges.  I am assuming you are already familiar with how this method works.

    SPSecurity.RunWithElevatedPrivileges(delegate()

    {

        using (SPSite currentSiteCollection = new SPSite("http://myserver/mysite"))

        {

            using (SPWeb currentWeb = currentSiteCollection.OpenWeb())

            {

                // unsafe updates are required to be able to write to the property bag

                currentWeb.AllowUnsafeUpdates = true;

     

                // you must check to see if the collection has a value in the assigned key already

                if (!currentWeb.AllProperties.ContainsKey(key))

                    currentWeb.Properties.Add(key, value.Value.ToString());

                else

                    currentWeb.AllProperties[key] = value;

     

                // update the properties

                currentWeb.Properties.Update();

                currentWeb.AllowUnsafeUpdates = false;

            }

        }

    });

    There is a complete example.  One thing to remember is that the method inside RunWithElevatedPrivileges requires that you create a new SPSite object for it to work.  Do not pass it a reference from one that already exists.  Create a new one by passing it a URL.  Since we need the SPWeb object in this case, I have things nested like I have mentioned in my post in the past.

    Luckily, reading from the property bag is not as difficult.  Just check to see if the key exists and read from it using AllProperties.

    if (currentSite.AllProperties.ContainsKey("MyItem"))

        myItem = currentSite.AllProperties["MyItem"].ToString();

    This may seem like a lot to store and retrieve values in the property bag, but it's pretty easy to wraps this code up into a class.

  • School of Dev was fun (Slides Attached)

    I had a good time at School of Dev this weekend.  It had a fairly decent crowd consider this was its first time and it was on Mother's Day weekend.  There was some good info there and I think just about everyone got a prize or two.  I had the opportunity to give my updated talk on Searching Business Data with MOSS 2007 Enterprise Search.  In the talk, I walked through how to search line of business data exposed through a web service.  My slides are attached to this post.

    I also had the opportunity to catch a couple of other SharePoint sessions.  I made it to Dennis Bottjer's session on Developing a Public Facing Internet Portal with MOSS 2007.  I also caught Becky "MOSS Lover" Isserman's session on using AJAX in SharePoint.  All in all I had a good time and I enjoyed the opportunity to be able to talk with other SharePoint developers.

    I am looking forward to TechFest 2008.  I am currently working on a new talk on using Code Access Security with SharePoint which hopefully I will be giving at a user group meeting fairly soon.

More Posts Next page »
2008 dotnetmafia.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems